Non-Functional Requirements
The performance, security, and reliability targets that are invisible until they're missed.
Why
Functional requirements say what the system does; non-functional requirements say how well it does it. NFRs are the difference between a feature that demos well and one that survives production: a 10-second response time 'works' in the demo and fails in the real world. Writing NFRs explicitly — p99 latency < 200ms, 99.9% uptime, PII encrypted at rest — makes them testable and prevents the 'we'll optimize later' trap that ships slow, insecure MVPs. NFRs are also what capacity planning and cost estimation are built on.
When
Alongside functional requirements in the PRD. Takes 1-2 hours for a feature. Skip only for internal tools with no SLA expectations.
How
1. For each NFR category, write a specific, measurable target: - Performance: "p99 API latency < 200ms at 1000 RPS" - Reliability: "99.9% uptime monthly, RTO < 5min, RPO < 1min" - Security: "PII encrypted at rest (AES-256), TLS 1.3 in transit" - Scalability: "handle 10x current load without code change" - Observability: "all user actions logged with trace_id within 1s" 2. For each target, note how it will be verified (load test, chaos test, audit) 3. Mark which NFRs are launch-blocking vs. post-launch hardening 4. Get sign-off from the on-call team — they own the reliability NFRs 5. Add NFRs to the ADR if they drive an architectural choice
Example
NFRs for the invoice-export feature: - Performance: CSV generation for 10k invoices completes < 5s (p99) - Reliability: export never blocks the main billing flow (async queue) - Security: export URL is signed, expires in 10 min, logged for audit - Scalability: queue depth visible in dashboard; alert at 1000 pending - Observability: each export emits a trace with user_id, row_count, duration Verification: - Performance: k6 load test at 50 concurrent exports - Reliability: chaos test — kill export worker, confirm billing unaffected
Common Problems
- ⚠Writing NFRs as aspirations ('should be fast') instead of measurable targets — 'fast' cannot be tested or alerted on.
- ⚠Treating all NFRs as launch-blocking — this stalls launches; triage which are truly blocking vs. hardenable post-launch.
Best Practices
- ✓Express performance as percentiles (p99, p99.9), not averages — averages hide the tail that users actually experience.
- ✓Tie each NFR to a runbook or alert so the on-call team can act when it's violated, not just notice it in a dashboard.