Skip to content

Non-Functional Requirements (Quality Attributes)

A Non-Functional Requirement (NFR) describes how well a solution must behave — how fast, how available, how secure — as opposed to what it must do.

  • What it is: Explicit, measurable targets for quality attributes like performance, availability, security, and scalability, agreed before design is finalized.
  • Why it matters: a system that does the right thing but falls over under real load, or fails a security review, has still failed — NFRs are what stop that from being a surprise.
  • For developers: NFRs are the numbers behind “why does this need a cache” or “why can’t we just query the primary database directly” — if a design decision seems arbitrary, check whether an NFR is driving it.
  • When to use it: alongside Solution Options Analysis, since the right option often depends on the quality bar the solution must hit.
  • When not to use it: as a copy-pasted checklist applied without reference to the actual problem — an internal reporting tool doesn’t need the same availability target as a payment system.
  • Prerequisites: a scoped problem statement.
graph TD
    NFR["Non-Functional Requirements"]
    NFR --> Perf["Performance (latency, throughput)"]
    NFR --> Avail["Availability (uptime, recovery time)"]
    NFR --> Sec["Security (auth, data protection)"]
    NFR --> Scale["Scalability (peak load, growth)"]

NFRs group into a handful of recurring categories. Each one needs a specific, measurable target for a given solution — not a generic “should be fast and secure.”

  1. For each NFR category (performance, availability, security, scalability, and any others relevant to the problem), ask what “good enough” actually means for this specific solution.
  2. Turn each answer into a measurable target — a number or a testable condition, not an adjective.
  3. Base targets on real data where possible (current peak traffic, existing SLAs, compliance requirements) rather than guessing.
  4. Record the targets alongside the chosen option’s ADR so they’re checked during design and testing, not just at launch.
  5. Revisit targets if the scope or expected load changes.

Continuing the retail checkout scenario, scoped around flash-sale traffic.

  • Input: a vague instruction to make checkout “reliable and fast.”
  • Process: pull actual flash-sale traffic data (5x normal peak) and the business’s tolerance for downtime during a sale.
  • Output: measurable NFRs — 99.95% availability during sale windows, p95 latency under 300ms for payment confirmation at 5x normal load, and PCI-DSS-aligned handling of card data.
  • Enforcing the target in code: a latency NFR is only real if something in the call path actually bounds it. A Polly timeout policy around the payment gateway call turns “p95 under 300ms” into an enforced budget rather than a hope:
var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(300));
var response = await timeoutPolicy.ExecuteAsync(
ct => paymentClient.ConfirmPaymentAsync(request, ct),
cancellationToken);

Validate the target itself with Azure Load Testing run at 5x normal peak, and monitor the live p95 in Application Insights so a regression is caught before customers notice.

  • Enforcing an availability NFR across regions: a 99.95% availability target usually can’t be met from a single Azure region alone — a regional outage breaches it regardless of how well the app itself is built. Two mechanisms make the target enforceable rather than aspirational:

    Scale-out under load — an Azure App Service autoscale rule adds instances as CPU rises, keeping the p95 latency target stable during the flash sale itself:

    resource autoscale 'Microsoft.Insights/autoscalesettings@2022-10-01' = {
    name: 'checkout-autoscale'
    location: location
    properties: {
    targetResourceUri: appServicePlan.id
    enabled: true
    profiles: [
    {
    name: 'CPU-based scale-out'
    capacity: { minimum: '2', maximum: '10', default: '2' }
    rules: [
    {
    metricTrigger: {
    metricName: 'CpuPercentage'
    metricResourceUri: appServicePlan.id
    timeGrain: 'PT1M'
    statistic: 'Average'
    timeWindow: 'PT5M'
    timeAggregation: 'Average'
    operator: 'GreaterThan'
    threshold: 70
    }
    scaleAction: { direction: 'Increase', type: 'ChangeCount', value: '1', cooldown: 'PT5M' }
    }
    ]
    }
    ]
    }
    }

    Surviving a regional outage — an Azure SQL Database auto-failover group spans two Azure paired regions (e.g. East US/West US), so a full region loss fails over automatically to a secondary replica without breaching the availability target:

    resource failoverGroup 'Microsoft.Sql/servers/failoverGroups@2022-05-01-preview' = {
    parent: primarySqlServer
    name: 'checkout-failover-group'
    properties: {
    readWriteEndpoint: { failoverPolicy: 'Automatic', failoverWithDataLossGracePeriodMinutes: 60 }
    partnerServers: [ { id: secondarySqlServer.id } ]
    databases: [ checkoutDatabase.id ]
    }
    }

    If the payment confirmation data instead lived in Azure Cosmos DB, the equivalent mechanism is enabling multi-region writes on the account rather than a failover group — useful when the NFR calls for near-zero RPO across regions rather than an automatic-but-brief failover window.

Use case When to use Notes
Comparing solution options Options score differently against a quality bar Feed NFRs into Solution Options Analysis rather than deciding on cost alone
Load testing before launch Validating the design actually meets its targets Test against the specific NFR numbers, not a vague “make sure it can handle load”
Vendor/SaaS evaluation Choosing a third-party component Check the vendor’s published SLAs against your own NFRs before assuming they match
  • Make targets testable: “fast” is not an NFR; “p95 latency under 300ms at 5x peak load” is.
  • Tie targets to actual risk tolerance: a batch reporting job and a payment system should not share the same availability target — calibrate to business impact.
  • NFRs can conflict: maximizing availability and minimizing cost often pull in different directions; document the trade-off rather than silently picking one.
  • Use ISO/IEC 25010 as the source taxonomy, not an ad hoc list: the four categories above are a practical subset of the international standard’s eight software quality characteristics — performance efficiency, reliability, security, compatibility, usability, maintainability, portability, and functional suitability. Checking against the full standard catches categories a quick brainstorm tends to miss, like maintainability or compatibility.
  • Write NFRs as quality scenarios, not adjectives: arc42’s quality-scenario technique states each target as stimulus → response — e.g. “when traffic reaches 5x normal peak (stimulus), p95 payment confirmation latency stays under 300ms (response)” — which is inherently testable, unlike a standalone number with no triggering condition.
  • Replication is the general mechanism behind autoscale and failover: Martin Fowler’s Patterns of Distributed Systems catalog documents this as the Leader and Followers pattern — one replica accepts writes and propagates them to followers, which is what both SQL failover groups and Cosmos DB replication implement under the hood. Recognizing the general pattern makes it easier to reason about the failover window and consistency trade-offs regardless of which Azure service implements it.
Term Meaning
NFR (Non-Functional Requirement) A measurable quality target (performance, availability, security, scalability) rather than a feature
p95 latency The response time under which 95% of requests complete — a common performance target
SLA (Service Level Agreement) A committed, measurable target for availability or performance, often with consequences for missing it
ISO/IEC 25010 The international standard defining the eight software quality characteristics NFRs are typically drawn from
Quality scenario An NFR written as stimulus → response (e.g. “at 5x peak load, p95 latency stays under 300ms”), arc42’s technique for making a quality target testable
Autoscale rule A rule that adds/removes compute instances based on a live metric like CPU, keeping a performance NFR stable under load
Failover group An Azure SQL Database construct that automatically fails over to a paired-region replica if the primary region becomes unavailable
Paired region Two Azure regions (e.g. East US/West US) linked for sequenced platform updates and disaster recovery
Symptom Likely cause Fix
Design decisions seem arbitrary NFRs were never made explicit or measurable Write down specific, testable NFR targets and reference them in design reviews
System passes functional tests but fails in production NFRs weren’t tested, only features were Add load, security, and failover testing against the stated NFR targets
Every requirement says “must be fast and secure” NFRs copied from a generic checklist Recalibrate targets to this specific solution’s actual risk and traffic profile
  • Why is “the system should be reliable” not yet a usable NFR?
  • Take a system you work on and write one measurable NFR for performance and one for availability.

Part of Introduction to Solution Architecture. Works alongside Solution Options Analysis & Trade-offs and feeds into Component & Integration Design. See also the Glossary. For the fuller operational treatment of autoscaling and multi-region failover, see Capacity & Scalability Planning and Business Continuity & Disaster Recovery.