Intro to System Design6 min · 4 of 64

Functional vs non-functional requirements

Split an ambiguous prompt into what the system must do and how well it must do it, then turn the second half into numbers your design can be checked against.

Every system design prompt hides two kinds of requirement, and they drive two different parts of the design. Functional requirements decide which components exist. Non-functional requirements decide how many of each, where they sit, and what they cost. Collect only the first half and you get a diagram that is arguably correct and impossible to size.

What the system does

Functional requirements describe the features, operations and services the system offers. They read as actions a user or another system can take.

  • A user can register with an email address and password.
  • A user can post a message to a timeline.
  • A user can search for products by keyword.
  • A user can add an item to a shopping cart.

They are documented as use cases or user stories, and on a whiteboard they become the API surface: each becomes an endpoint, a request and response shape, and part of the data model. One that produces no endpoint and no schema entry is not yet specific enough to design against.

How well it does it

Non-functional requirements describe the qualities and constraints of the system — the "quality attributes", or the "-ilities".

  • Handle 10,000 requests per second. (Scalability)
  • Sustain 99.99% uptime. (Availability)
  • Serve reads at p99 under 200 ms. (Performance)
  • Encrypt sensitive fields at rest and in transit. (Security)
  • Run on more than one cloud provider. (Portability)
  • Allow a new feature to ship without touching unrelated services. (Maintainability)

To find them, look for adjectives and adverbs: fast, secure, reliable, scalable, easy to use. Each is a defect until a number is attached to it.

"The system should respond in under 2 seconds" is not yet a requirement: it names no percentile, no load, and no measurement point. Averages hide the tail, and the tail is what users feel. The designable version is "p99 read latency under 200 ms, measured at the client, at 1,000 QPS peak" — a claim that can be violated, which is what makes it useful.

The split drives different decisions

Functional requirements tell you which components exist — a store for accounts, a service that resolves a short code. Non-functional requirements tell you how many of each, how they are configured, and where they run.

Functional requirements decide what the boxes are. Non-functional requirements decide how many, and whether they need replicas, a cache, or shards.
From an ambiguous prompt to a sized topologywhat itdoeshow muchtarget not met: revisitAmbiguouspromptFunctional listverbs the systemdoesNon-functionaltargetsQPS · p99 · ninesAPI + datamodelEstimateQPS, storage,bandwidthTopologyreplicas · cache ·shards

Scroll to zoom · drag to pan · 0 fits · Esc closes

The dotted edge is the part candidates skip: when the sized topology cannot hit the target, renegotiate the number rather than keep adding boxes. "We can hold p99 under 200 ms from one region, or under 100 ms globally with edge caching and 30 seconds of staleness. Which do you want?"

Worked example: a URL shortener

Functional:

  • Accept a long URL and return a short one.
  • Redirect a short URL to the original.
  • Optionally, let a user choose a custom short code.

Non-functional, as they usually arrive: handle millions of URLs, be highly available, redirect fast, block malicious links. None of those is designable yet. Assume 100 million new links a month and a 100:1 read-to-write ratio, and the arithmetic falls out.

Writes: 100,000,000 / month / 30 days ~= 3.3M/day
        3.3M/day  ~= 40 QPS average          (1M/day ~= 12 QPS)
Reads:  40 QPS x 100                = 4,000 QPS average
        4,000 x 3 (peak factor)     = 12,000 QPS peak

Storage: 500 bytes/record x 100M/month = 50 GB/month
         50 GB x 60 months            = 3 TB over five years

Two conclusions, both worth points. A single commodity Postgres box handles roughly 5,000 simple QPS, so reads alone sit near that ceiling on an average day and pass through it at peak: a cache in front of the lookup is load-bearing, and redirects are a good fit because the same hot keys repeat. Storage is not the problem — 3 TB over five years fits on one machine — so the pressure to shard comes from request rate, not volume.

Now the latency target. "Redirect in under 100 ms at p99" is a placement decision before it is a database decision. A round trip from India to US East is roughly 200 ms, so a user in Mumbai hitting a single us-east-1 deployment misses the target however fast the lookup is. Inside a datacenter a round trip is about 0.5 ms, so the whole server-side path costs a few milliseconds. The budget goes on distance, which points at a CDN, not a faster query.

And 99.99% availability — about 52 minutes down a year — rules out one app server and one database, because a single deploy spends a month's budget. That number is what buys redundancy.

The trade-offs are between non-functional requirements

Functional requirements rarely conflict; you can build both features. Non-functional ones conflict constantly, and naming the conflict is the senior move.

Encrypting every field costs CPU and rules out indexing that column, so search slows down. Synchronous replication to a second region protects data by adding a cross-region round trip to every write. A requirement to stay writable while the network is partitioned chooses availability over consistency for the duration of that partition; with no partition, it returns as latency against consistency — see consistency models.

In an interview

Clarifying both kinds of requirement is what the opening minutes are for, and failing to address the non-functional half is a common way to lose the round. The interviewer is testing whether you can turn an underspecified prompt into constraints before you draw, because every later justification depends on those numbers.

Ask the functional questions first and write the answers as a short list, including what is explicitly out of scope. Then ask the non-functional ones as numbers, not adjectives: "How many daily active users, and what read-to-write ratio? What latency target, at which percentile? What availability? Is stale data acceptable on the read path, and for how long?" The rest of the hour is in what a design interview actually tests.

The specific mistake that loses points is collecting non-functional requirements as adjectives — "it should be scalable and highly available" — and never converting them into figures. Every later decision then reads as a guess, because no component has a target to be checked against. The second is treating the numbers as fixed: "do we need read-your-writes here, or is a few seconds of staleness acceptable?" often buys a much simpler design.

Check yourself

A product manager asks for 99.99% availability. The current design is one app server and one Postgres box. How much downtime does that requirement allow, and what is the first thing that breaks it?

99.99% of a year leaves about 52 minutes of downtime, roughly 4.3 minutes a month. A single instance of anything blows that on its first rolling deploy, before any hardware fails. Meeting it takes at least two app servers behind a load balancer and a follower ready for promotion. If the business will not pay for that, say so and negotiate to 99.9%, about 43 minutes a month.

The prompt is a feed that must "feel instant" for users in India, served from us-east-1. What p99 can you promise, and what would you change?

A round trip from India to US East is roughly 200 ms, so a p99 under 200 ms is unreachable however fast the backend is, and a cold TLS connection adds round trips. Either relax the target to roughly 350 ms, or move the read path closer with edge caching or a regional replica and accept a staleness window. Naming distance as the binding constraint is the answer; tuning the query is not.

Same URL shortener, but the prompt says 10 million new links a month instead of 100 million. Does your design change? Show the arithmetic.

10M/month is about 330K/day: roughly 4 QPS of writes and 400 QPS of reads at 100:1, call it 1,200 QPS at a 3x peak. That is under the roughly 5,000 simple QPS one commodity Postgres box serves, and storage is 5 GB/month, so one primary plus a cache for hot keys, and no shards. Name the trigger that would change it: sustained peak above about 4,000 QPS, or a working set that stops fitting in RAM.