Skip to content

    System Design

    System Design: Requirements and Scope

    Learn to turn a vague product idea into clear scope and measurable goals: functional and non-functional requirements, latency percentiles, availability nines, and SLOs.

    By Published 14 min read

    Share

    This lesson is part of System Design Foundations: From Zero to Your First Design. Enroll for free, then sign in, to read it in full.

    What you'll learn: By the end of this lesson you will be able to separate functional from non-functional requirements and ask the clarifying questions that define scope. You will be able to state latency, availability and durability goals with real numbers, and explain how each requirement pushes a design in a particular direction.

    Imagine you walk into an architect's office and say, "I want a house." A good architect does not start drawing. She asks how many people will live there, what the budget is, whether anyone uses a wheelchair, how much land you have, and whether you plan to add a floor later. A house for two people in a warm climate and a house for eight in the mountains are both houses, but they share very little.

    Software is the same. "Build an invoicing product" is a wish, not a specification. And "make it fast and reliable" is the most common sentence in software and the least useful, because nobody can test it. The most valuable thing you can do at the start of any design is turn wishes into short, precise statements you can check a design against. This lesson shows how.

    The core idea

    Requirements come in two kinds, and a design is only good relative to both.

    Functional requirements say what the system does: the features a user can see. "A user can create an invoice." "A customer can download the invoice as a PDF."

    Non-functional requirements (NFRs) say how well it does it: speed, availability, durability, security, cost, and how easy it is to change. "Creating an invoice responds in under 400 milliseconds for 95% of requests." "Once an invoice is saved, it is never lost."

    Functional requirements tell you which boxes to draw. Non-functional requirements tell you how many of them, how they are connected, and how much they cost. Two systems with identical features and different NFRs are different designs, and a feature list alone almost never forces a particular architecture. The NFRs do.

    From a vague idea to a scope

    A scope is a fence: what is in, what is out, and what is deferred. Without one, the design grows until you cannot finish it. Write the idea in one sentence, then list user actions. For a small invoicing product:

    • In scope: create and edit invoices, generate a PDF, email it to the client, record a payment, see an overdue list.
    • Out of scope for now: tax filing, multi-currency, a mobile app.
    • Later: recurring invoices, accounting-software integrations.

    The "out of scope" list is a promise. It protects you from the endless "what about multi-currency?" question.

    Clarifying questions to ask

    Ask in groups. These work for nearly any product.

    Group Questions to ask
    Users Who uses it? How many in total and per day? One country or worldwide?
    Features What are the three or four must-have actions? What is not needed in version one?
    Usage pattern Read-heavy or write-heavy? Are there spikes (month-end, a launch, an event)?
    Data How big is one item? How long is data kept? Is any of it regulated or private?
    Quality How fast must it feel? What happens if it is down for ten minutes? Can a user see slightly old data?
    Constraints Budget, team size, deadline, existing technology, compliance, regions where data must stay.

    You rarely get exact answers. When none comes, state an assumption out loud and move on: "I will assume 800 business customers and I will revisit this if it is wrong." A stated assumption can be corrected cheaply. A hidden one produces a surprise later.

    The non-functional requirements checklist

    Here are the qualities worth quantifying. Each deserves a number, even a rough one. Use this test on every vague phrase.

    flowchart TD V["Vague phrase such as fast or reliable"] --> Q1{"Can you name the user action it applies to?"} Q1 -->|no| A1["Ask which journey matters most"] A1 --> Q1 Q1 -->|yes| Q2{"Is there a number and a unit?"} Q2 -->|no| A2["Write a labelled assumption"] A2 --> Q3 Q2 -->|yes| Q3{"Is the percentile and time window stated?"} Q3 -->|no| A3["Add for example p95 over 30 days"] A3 --> Q4 Q3 -->|yes| Q4{"Can you say what design choice it changes?"} Q4 -->|no| A4["It is a preference, so sharpen or drop it"] Q4 -->|yes| OK["A usable requirement"]

    How to read it: a phrase is only a requirement once it passes all four questions: which action, what number, which percentile and window, and what it changes.

    Latency: use percentiles, not averages

    Latency is how long a request takes from the user's point of view. An average hides the pain. If 99 requests take 100 milliseconds and one takes 10 seconds, the average is about 200 milliseconds, yet one user in a hundred waited ten seconds. So we use percentiles:

    • p50 (the median): half of requests are faster. The typical experience.
    • p95: 95% are faster; one in twenty is slower.
    • p99: 99% are faster; one in a hundred is slower. This is the "tail".

    A requirement should read like "p50 under 100 ms, p99 under 500 ms for the invoice list endpoint". The tail matters more than it seems, because one page view often triggers many backend calls. If a page makes twenty calls and each has a 1% chance of being slow, about 18% of page views (1 minus 0.99 to the power 20) hit at least one slow call.

    Throughput

    Throughput is work per unit time: requests per second, invoices per hour. Quote it for the average and the peak, since you build for the peak. The Estimation lesson shows how to derive it.

    Availability and the nines

    Availability is the fraction of time the system works, quoted in "nines" (approximate, for a 365-day year).

    Availability Common name Downtime per year (approx.)
    99% two nines about 3.65 days
    99.9% three nines about 8.76 hours
    99.99% four nines about 52.6 minutes
    99.999% five nines about 5.26 minutes

    Each extra nine cuts allowed downtime by ten and raises cost steeply. Two nines to three may mean running more than one copy of everything. Four or five usually means multiple data centres, automated failover and careful operations. Match the number to the cost of downtime: a photo app down for an hour annoys people, a payment processor that is down for an hour loses money and trust.

    Durability and consistency

    Durability means data, once acknowledged as saved, is not lost. It is separate from availability: a system can be down for an hour and lose nothing, or be up all the time while quietly losing data, which is a disaster. "A saved invoice never disappears" pushes you toward replicated storage and tested backups.

    Consistency asks: after a write, when do readers see it? Strong consistency means immediately everywhere. Eventual means soon, not instantly. A dashboard total can lag by seconds. "Is this invoice number already used?" cannot. Decide per feature, because strong consistency costs latency and availability. See CAP and Consistency.

    The rest of the list

    • Security and privacy: who may see which invoice, how data is encrypted, which laws apply.
    • Scalability: how load grows and whether the design must absorb it without a rewrite.
    • Cost: a monthly budget turns "store everything forever in the fastest tier" into a decision.
    • Maintainability: a simple design three people can run beats an elegant one that needs thirty.

    How requirements drive architecture

    A requirement is useful when you can say what it changes in the design.

    graph LR R1["Low p99 latency"] --> D1["Caching and CDN"] R2["High availability"] --> D2["Redundancy and failover"] R3["Durability of saved data"] --> D3["Replicated storage and backups"] R4["Read-heavy traffic"] --> D4["Read replicas and caches"] R5["Traffic spikes"] --> D5["Queues and autoscaling"] R6["Strict consistency"] --> D6["Single leader or transactions"]

    A CDN keeps copies of files near users, a replica is a copy of the database, failover means switching to a standby when the main part fails, and a queue holds work for later. Each gets a full lesson soon.

    • p99 under 500 ms with users worldwide suggests a CDN and caches near the application. One region serving everyone cannot beat the speed of light.
    • 99.9% availability (about 8.76 hours a year) makes a single server that needs manual repair a poor bet: one bad night can use up the whole year's allowance. You will usually want at least two application instances and a database with a standby.
    • Read-to-write ratio of 100 to 1 points to caches and read replicas. A write-heavy workload points to partitioned, append-friendly storage.
    • Spiky traffic favours queues: absorb the burst, process at a steady pace.
    • Strong consistency for payments points to a transactional relational database, not lagging replicas.
    NOTE

    Requirements often conflict. Lower latency can mean serving cached, slightly stale data. Higher availability can mean accepting temporary inconsistency. Cheaper storage can mean slower access. Naming the conflict out loud, and choosing a side with a reason, is what design is.

    For the photo-sharing running example, the same discipline gives a table like this. Every number is an illustrative assumption.

    Quality Requirement (assumed) Why
    Latency Feed p50 under 150 ms, p99 under 600 ms People abandon slow feeds
    Availability 99.9% for viewing, 99.5% for uploading Viewing is the core; uploads can retry
    Durability Acknowledged uploads are never lost Photos are irreplaceable
    Consistency Counts may lag seconds; you see your own upload immediately Lag is invisible, a missing own photo looks like a bug

    Notice the deliberate asymmetry: less availability for uploads than viewing. That is a design decision, and it saves money.

    In practice: an invoicing SaaS turns "fast and reliable" into SLOs

    NOTE

    A realistic scenario, not a case history. This is a composite example built from common production patterns. It does not describe any specific company, and every number in it is an illustrative assumption.

    The situation

    A B2B invoicing product serves about 800 small businesses. Eight engineers run it on managed Kubernetes: a Node.js API, PostgreSQL, Redis, RabbitMQ for background jobs, and S3-compatible storage for invoice PDFs, with Prometheus and Grafana for metrics. Sales has just signed the first larger customer, whose contract says the service must be "fast and reliable" and carries a 99.9% availability clause. Nobody can say whether the product meets it today.

    Assume about 60,000 invoices a month, 70% of them in the last three days of the month. The pain is vague but real: support hears "invoices are slow" at month-end, and one incident left PDFs undelivered for three hours before anyone noticed, because every dashboard was green on CPU and memory.

    Starting architecture

    Invoicing SaaS, starting architectureCustomer to Ingress; Ingress to Node.js API; Node.js API to PostgreSQL; Node.js API to RabbitMQ; RabbitMQ to PDF worker; RabbitMQ to Email worker; PDF worker to Object storage; Email worker to Email providerASYNCHRONOUS PARTCustomer: web or API clientCustomerweb or API clientIngressIngressNode.js API: synchronous partNode.js APIsynchronous partPostgreSQLPostgreSQLPDF workerPDF workerRabbitMQ: background jobsRabbitMQbackground jobsEmail workerEmail workerObject storage: invoice PDFsObject storageinvoice PDFsEmail providerEmail provider

    How to read it: the user-visible journey has a synchronous part (create invoice through the API) and an asynchronous part (PDF and email through workers). Each part needs its own requirement.

    Options considered

    Option What it fixes What it costs Risk
    Keep the wording, add more dashboards Visibility of CPU and memory Almost nothing Still no definition of "reliable"; the contract stays unprovable
    Define SLIs and SLOs for the critical journeys, measure a baseline, then alert on error-budget burn A testable definition, and alerts that match what users feel Instrumentation work and a few weeks of measuring Setting a target tighter than the platform can deliver
    Promise 99.99% with multi-region failover Looks strong in the contract A second region and active-active data, a large budget Cost and complexity with no customer need behind them

    The decision

    Field Record
    Context A 99.9% clause and a vague "fast and reliable" promise. A team of eight with no on-call specialist.
    Decision Define four SLOs on user journeys, measure for four weeks before committing, and set the contractual SLA below the internal SLO.
    Consequences Good: arguments about "slow" end; alerts follow user pain. Bad: instrumentation effort, and a month-end SLO miss will now be visible and countable.
    Revisit when The error budget is exhausted in two consecutive months, or a customer needs more than 99.9%.

    The four SLOs, each with its indicator, target and window:

    Journey SLI (what is measured) SLO Window
    Create invoice Share of API requests that succeed in under 400 ms 99.5% 30 days
    Availability Share of successful requests (non-5xx) at the ingress 99.95% 30 days
    PDF ready Share of invoices whose PDF exists within 60 s of creation 99% 30 days
    Durability Acknowledged invoices missing after a restore test zero, checked by a restore test quarterly drill

    The internal availability SLO (99.95%, assumed to be what the baseline supports) is deliberately tighter than the 99.9% in the contract. Over 30 days it gives an error budget of about 22 minutes (43,200 minutes multiplied by 0.05%); the contract's 99.9% would allow about 43.

    Rollout and verification

    flowchart LR B["Baseline for 4 weeks"] --> D["Draft SLOs published internally"] D --> A["Burn-rate alerts to the on-call"] A --> R["Monthly review of budget and targets"] R --> S["Sign the SLA below the SLO"]

    Measure first, commit later. For four weeks record the indicators in Prometheus without alerting, then set each SLO just inside what the baseline shows. Alert on budget burn, not on raw errors: a fast alert pages the on-call if the last hour burns budget at about 14 times the sustainable rate (that would spend 2% of a 30-day budget in an hour), and a slow alert opens a ticket for a steady leak. Add a queue-age metric, the age of the oldest unprocessed PDF job, because it is the one that would have caught the three-hour incident. Verify by replaying last month's traffic shape in a staging load test and by restoring last night's backup into a scratch database.

    The 2 a.m. failure

    The page reads "PDF ready SLO fast burn: 41% of invoices older than 60 s without a PDF". The cause: a customer uploaded a template with a 40 MB embedded logo, the PDF worker ran out of memory and was killed, the message was redelivered, and the next worker crashed on it too.

    sequenceDiagram participant Q as RabbitMQ participant W as PDF worker participant K as Kubernetes Q->>W: Deliver job with huge logo K--xW: Pod killed for out of memory Note over Q: Unacknowledged job is requeued K->>W: Start replacement pod Q->>W: Redeliver the same job K--xW: Pod killed again

    First three checks: the queue-age panel in Grafana, kubectl get pods -l app=pdf-worker (looking for OOMKilled and restarts), and rabbitmqctl list_queues name messages_ready messages_unacknowledged to see the backlog. Mitigation: pause the consumer, move the poison message to a dead-letter queue, and let the backlog drain. Follow-up: cap template asset size at upload, set a maximum redelivery count with a dead-letter route, and give the worker a memory limit that matches the cap.

    Architect's notes

    • Measure before you promise. Avoid signing a number you have not observed for a few weeks. The baseline decides the target, not the other way round.
    • Set the SLA looser than the SLO. The gap is your safety margin and the room your team needs to ship changes.
    • Pick journeys, not components. "Database CPU" is a cause. "Invoice PDF ready in 60 s" is what the customer feels. Alert on the second.
    • Ask in the review: who is paged and what can they do? An SLO with no owner and no runbook is decoration.
    • Defer the extra nine. Cost grows faster than the benefit. Write down the trigger (an actual customer need) that would justify it.

    Common mistakes

    • Starting with technology. Choosing Kafka or microservices before the requirements locks in decisions with nothing to justify them. Fix: write requirements first, then pick tools.
    • Vague non-functional goals. "Fast", "scalable" and "reliable" cannot be tested. Fix: a number, a unit, a percentile and a window.
    • Quoting the average latency. It hides the slow tail users feel. Fix: state p50 and p99, and measure them.
    • Asking for five nines by default. Each nine multiplies cost. Fix: match availability to the real cost of downtime, per feature.
    • No explicit non-goals. Unlisted scope creeps in. Fix: write an "out of scope" list and refer back to it.
    • Treating requirements as fixed. Early numbers are guesses. Fix: record assumptions and revisit them.
    • Confusing availability with durability. Fix: set the two goals separately.

    Key takeaways

    • Two kinds of requirement. Functional says what the system does; non-functional says how well, and shapes the architecture most.
    • Scope is a fence. Write what is in, out and later, and keep the out list visible.
    • Ask in groups. Users, features, usage pattern, data, quality and constraints cover most questions.
    • State assumptions. When answers are missing, write one down so it can be challenged.
    • Percentiles beat averages. Use p50, p95 and p99; the tail is what users complain about.
    • Nines are expensive. Each extra nine cuts annual downtime tenfold and raises cost sharply.
    • Availability is not durability. Set separate goals for staying up and for never losing data.
    • An SLO is a requirement with an indicator, a target and a window. It implies an error budget the team can spend on risk.

    Practice exercises

    Exercise 1 — Starter (30 min): Take the idea "a service that shortens URLs." Write five clarifying questions you would ask, grouped using the table in this lesson. Then write three functional requirements and an out-of-scope list of at least three items.

    Exercise 2 — Intermediate (2 hours): For the invoicing product, convert each of these into a measurable requirement with a number, percentile and window (or a labelled assumption): "invoices should be fast," "PDFs should arrive promptly," "the site should almost never be down." For availability, use the nines table to compute the allowed downtime per month for your target (divide the yearly figure by 12) and decide whether a single server could ever meet it.

    Exercise 3 — Advanced (half day): Write a one-page requirements document for a food-delivery order-tracking feature. Include scale assumptions, a table of non-functional requirements with an SLI and SLO for each, and a "Conflicts" section listing two pairs of requirements that pull against each other (for example, freshness of location versus cost). For each conflict, state which side you favour and why. Then list which architecture choices (cache, queue, replica, region count) each requirement pushes you toward.

    Enroll to keep reading

    Lesson 2 of 12 · System Design Foundations: From Zero to Your First Design

    Learn to turn a vague product idea into clear scope and measurable goals: functional and non-functional requirements, latency percentiles, availability nines, and SLOs.

    In the rest of this lesson

    • How requirements drive architecture
    • In practice: an invoicing SaaS turns "fast and reliable" into SLOs
    • Architect's notes
    • Common mistakes
    • Key takeaways
    • Practice exercises

    Enrolling is free. It unlocks every lesson in System Design Foundations: From Zero to Your First Design, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments