Functional vs Non-Functional Requirements
11min
Every product spec eventually has the same two questions buried in it: what should the system do, and how well does it need to do it. Teams are usually good at writing down the first one — login, checkout, reports, the feature list everyone can picture. The second one gets skipped far more often than it should, and it's the part that quietly determines whether the thing actually survives contact with real users.
That second category is what non-functional requirements cover — performance, security, availability, scalability, and a handful of others. I've watched a perfectly functional feature get shipped and then fall over in production purely because nobody wrote down what "fast enough" or "available enough" actually meant. Let's go through both categories properly.
Two different questions
Functional requirements answer "what can the user or the system do." The user can log in with email and password. The API returns an order by ID. A confirmation email goes out when payment succeeds. These are observable behaviors — you can point at one and say whether it happened.
Non-functional requirements answer a different question entirely: "how well does it do that." The API responds within 200ms under normal load. Data at rest is encrypted. The system stays available 99.9% of the time. None of these describe a feature — they describe a constraint that applies across most or all of the features you already wrote down.
The short version: functional requirements define the product. Non-functional requirements define whether anyone can actually rely on it.
The short version, by category
Functional — features, user actions, business rules, integrations, the inputs and outputs people can observe. Usually written as user stories or use cases, and checked with acceptance tests.
Non-functional, the usual suspects:
- Performance — latency, throughput, response time
- Scalability — handling growth in load or data
- Availability & Reliability — uptime, failure tolerance, recovery time
- Security — auth, encryption, compliance
- Usability — how easy the thing is to actually use
- Maintainability — how cheap it is to change later
- Portability & Compatibility — which platforms, browsers, API versions
- Recoverability — backups, restores, disaster recovery
- Capacity — resource limits and cost
- Compliance — legal and regulatory obligations
- Interoperability — playing nicely with other systems
- Observability — logging, metrics, alerting
Functional requirements: what the system does
These are the easy ones to write, conceptually — given an input or action, the system should produce a defined result. Login, signup, profile edits, search, checkout dashboards. Business logic like discount rules and approval workflows. Integrations: "orders sync to the warehouse API every 5 minutes." Reporting: "admins can export a CSV of the last 30 days of orders."
They usually show up as user stories ("as a user, I can reset my password so I can regain access") or feature specs, and get checked with acceptance tests — if the feature behaves as specified, you're done. Where teams get burned is vague or missing functional requirements: that's where scope creep and "wait, that's not what I asked for" rework comes from.
Why non-functional requirements actually matter
Unlike a feature, an NFR rarely maps to one button or one endpoint — it cuts across the whole system. And that's exactly why it's so easy to skip: there's no obvious place to write it down, so it doesn't get written down, and then everyone has a different idea of what "fast" or "secure" means.
That gap shows up later as arguments with no shared definition, technology decisions made on gut feel instead of a target, and — eventually — outages, slow pages, or a security incident that could've been scoped for from day one. Treating NFRs as first-class, written-down, measurable requirements is really just trying to avoid shipping the right features with the wrong qualities.
Performance
Performance requirements are about speed and capacity — how quickly the system responds, and how much load it can carry while doing so. "The API responds within 200ms for p95 under normal load." "Search results show up within a second." "The service stays under 512MB RAM at typical load." The common thread is that these need to be numbers, not adjectives — "it should be fast" can't be tested, but "p95 latency under 200ms" can. Once you have a number, it starts driving real decisions: where to add caching, which queries need an index, whether something needs to move to a background job instead of blocking the request.
Scalability
Scalability is whether the system can absorb growth — more users, more traffic, more data — without costs or latency blowing up proportionally. "Supports 10x current traffic without an architecture change." "The database holds up to 100GB without query performance degrading." It's usually split into the horizontal and vertical flavors I wrote about in a separate post — and whichever you pick affects how stateless your services need to be, how you partition data, and where caches and queues sit in the picture. Setting a target early saves you from a painful redesign once the growth actually shows up.
Availability and reliability
These two get used interchangeably, but they're not the same thing. Availability is simply how much of the time the system is up and usable — 99.9% uptime, say. Reliability is whether it does the right thing while it's up — no lost payments, no silently wrong results, even under partial failure.
In practice that turns into things like: "the API is available 99.9% of the time per month, excluding announced maintenance." "Successful payments are never lost." "Recovery from a failed deploy takes under 15 minutes." That last one is your MTTR, and together these targets are what drive redundancy, health checks, failover, and graceful degradation — they're usually written up as SLAs when there's a contract involved, or SLOs when it's just an internal bar.
Security
Security requirements need to be concrete, not a vibe. Authentication: SSO or email/password, with rate limiting so brute-forcing isn't trivial. Authorization: users only see their own data, admins get role-based access. Encryption: TLS 1.3 in transit, sensitive fields encrypted at rest. Compliance: PCI-DSS if you're touching payment data, GDPR if you're touching EU personal data. Audit: sensitive actions get logged, full stop.
None of this is optional once you write it down — it should be testable the same way performance is, through pen tests and compliance audits, not just code review sign-off.
Usability
This is the NFR people forget applies to APIs too, not just UIs. "A first-time user can complete checkout without help." "The UI meets WCAG 2.1 AA." But also: "the API returns error codes and messages that actually tell you what went wrong," which is a usability requirement for the engineers integrating against you, not just end users. Validated through usability testing, accessibility checks, and — often skipped — someone unfamiliar with the API actually trying to use the docs.
Maintainability
How expensive is it to change this system later? "A new payment provider can be added without touching core order logic." "Critical paths have at least 80% test coverage." "Rollback is possible within minutes, not hours." This one doesn't show up as a bug report — it shows up as everything taking longer than it should, three years in, because nobody wrote it down as a requirement when the system was young.
Portability, compatibility, and interoperability
Three closely related concerns: where can this run (portability — "runs in Docker on Linux, no OS-specific assumptions"), what versions does it support (compatibility — "the last two major API versions, deprecations announced 6 months out"), and how well does it talk to other systems (interoperability — "exchanges data with Partner X via the agreed JSON schema over OAuth2"). Skip these and you end up with vendor lock-in or breaking changes nobody saw coming.
Recoverability and disaster recovery
Backups taken daily, point-in-time restore within 24 hours. An RTO (how long recovery takes) of 4 hours and an RPO (how much data you can afford to lose) of 1 hour. DR runbooks that get tested at least once a year — tested, not just written and forgotten. Without numbers like these, recovery from a real incident is improvised at 2am, which is exactly when you don't want to be improvising.
Capacity and cost
Less glamorous than the others, but just as real: a defined budget for CPU, memory, and egress; batch jobs that finish in their allotted window without starving online traffic; idle resources that actually scale down instead of just sitting there racking up cost. These requirements shape autoscaling policy and resource quotas more than any other category.
Compliance
This one isn't up for negotiation — it comes from outside the engineering team entirely. GDPR retention and consent rules if you handle EU personal data. PCI-DSS if you touch card data. Audit logs retained for however many years your regulator requires. Get this wrong and it's not a bug, it's a legal exposure.
Observability
If you can't see what the system is doing, you can't know whether any of the above targets are actually being met. Structured logs and metrics from every service. Alerts firing within minutes of a real failure, not after a customer complains. One-command rollback. Observability isn't really its own NFR so much as the thing that lets you verify all the others.
Making NFRs measurable: SLIs, SLOs, SLAs
The three letters are easy to mix up, so here's the short version: an SLI is the metric itself — "percentage of requests under 200ms." An SLO is the target for that metric — "99% of requests under 200ms over 30 days." An SLA is what you promise a customer, usually with a financial consequence attached — "99.9% uptime or service credits." Write your NFRs as SLOs backed by clear SLIs and they stop being aspirational and start being something you can actually review in a retro.
They're not really separate things
A login flow is a functional requirement, but it's also subject to several NFRs at once — it has to be secure, fast enough, and available, all simultaneously. Most NFRs don't attach to one feature; "every API meets this latency budget" applies everywhere at once, which is part of why they're easy to lose track of.
Sometimes they pull against each other directly — "real-time" might get relaxed to "near real-time" once someone does the math on what true real-time would cost. That's a legitimate trade-off, not a failure, as long as it's made on purpose instead of by accident. And the way you validate the two is genuinely different: acceptance tests for functional behavior, load tests and security audits and operational reviews for everything else.
Mistakes worth avoiding
The most common one is just not writing NFRs down at all — only the feature list gets documented, and qualities get figured out implicitly (badly) during an incident. Close behind that: writing them too vaguely to test. "Fast" and "secure" aren't requirements, they're wishes. "p95 latency under 200ms" and "TLS 1.3 with encrypted-at-rest sensitive fields" are requirements.
Beyond that — treating NFRs as nice-to-haves that get cut when the deadline is tight, never assigning them a measurable target, and not writing down the trade-offs you made between conflicting requirements (consistency vs latency, cost vs redundancy) so the next person doesn't have to reverse-engineer your reasoning. None of these mistakes are exotic. They're just easy to make when the pressure is to ship a demo, not a system.
The bottom line
Functional requirements are the feature list. Non-functional requirements are everything that determines whether that feature list survives contact with real traffic, real attackers, and real outages. Both deserve to be written down with the same rigor — and the moment NFRs get a number attached instead of an adjective, they stop being an afterthought and start actually shaping the architecture, which is the whole point of writing them down in the first place.