Give two competent teams the same feature list — the same user stories, the same screens, the same “the system shall” bullet points — and let them build in isolation. You will very often get back two systems that share almost no architecture. Same features, completely different shape.
How? The feature list was identical. What differed was everything nobody wrote down: how fast it had to be, how much load it had to survive, how quickly it had to recover, how easily it had to change. One team assumed a few hundred users; the other, a few hundred thousand. One assumed downtime was fine overnight; the other, never. Those assumptions — not the features — decided the architecture. Which is the whole point: the features tell you what the system does; the non-functional requirements decide what it has to be.
The worst name in software
“Non-functional requirements” is a terrible label. It makes them sound like the optional extras — the stuff you get to after the real work. Some people prefer “quality attributes”, which is better, or just “the -ilities”: scalability, availability, reliability, maintainability, observability, security, and their friends. Whatever you call them, they answer a different question from the feature list. Features are what the system does. NFRs are how well, how fast, how safely, and for how long.
And here is the claim I want to defend: the architecture is, more or less, the set of decisions you made to satisfy the non-functional requirements. Features are portable — you can bolt almost any feature onto almost any structure. It’s the NFRs that force the structure. Show me a system that must stay up during deploys, survive a data-centre loss, and be safely changed by someone who joined last week, and I can predict a great deal about its architecture before I’ve seen a single feature. Show me the feature list alone and I can predict almost nothing.
They conflict — that’s the entire game
If NFRs were free you’d just max them all out. They’re not, because they pull against each other, and the pulling is where architecture actually happens.
- Availability versus consistency. The old CAP lesson: when the network partitions, you get to keep serving or keep everyone agreeing, not both. Which you pick reshapes your data layer.
- Security versus usability. Every control you add is friction you spend somewhere.
- Performance versus simplicity. Caches, denormalisation and read models buy speed with complexity you’ll maintain forever.
- Cost versus all of the above. Five-nines availability is a different budget from three, and most of the difference is architecture.
You cannot maximise all of them, so you rank them and you optimise the ones that matter for this system. Which is, of course, the same thing this blog says about everything: it comes down to what you’re optimising for. Your top two or three quality attributes are what you’re optimising for — and everything downstream in the design is the bill for that choice.
The failure mode: NFRs by default
Here’s how it actually goes wrong. Nobody writes the NFRs down. The feature backlog is exhaustively groomed; the “-ilities” live in nobody’s head in particular. So they don’t get decided — they get defaulted. The system quietly ends up with whatever performance the first implementation happened to have, whatever availability the platform gives you for free, whatever security you remembered on the day.
Then production teaches you what your NFRs were all along. The 2 a.m. page is an availability requirement you never stated. “Why is this screen so slow?” is a performance target discovered by its absence. “We can’t change this without breaking three other things” is a maintainability requirement that was never anyone’s job. You end up with an architecture nobody chose — assembled out of a hundred implicit defaults — and you find out its properties by being hurt by them.
The tell is that these conversations always happen in the past tense. Mature teams have them in the future tense, on purpose, before the code exists.
Treat them as first-class, because they already run the show
The fix isn’t heavy. It’s to drag the NFRs into the light and give them the same status the feature list already enjoys.
- Name and rank them. “Handle 10k concurrent users, recover in under five minutes, be changeable by a new joiner inside a day” is worth more than a folder of diagrams. Ranking matters more than the numbers — it says which conflicts you’ll resolve in whose favour.
- Record the decisions they drive. Nearly every load-bearing choice traces back to a quality attribute. That’s exactly what an architecture decision record is for — “we split this out because the availability target demanded it” is the sentence the next person needs.
- Make them executable where you can. An architecture fitness function is really just a non-functional requirement someone cared about enough to turn into a test — a latency budget, a dependency rule, a coverage gate. An NFR you can assert against is an NFR that won’t quietly rot.
Security, incidentally, is just one of these attributes among many — an important one, with a whole discipline attached, but subject to the same “how much is appropriate?” logic as all the others. That’s a thread worth its own posts, and it gets them.
So the next time someone calls them “non-functional”, push back a little. They are the most functional requirements you have — they’re the ones that build the building. The features are just what happens inside it.