Requirements Engineering

A client walks into your team's office and says: "I want an app so my customers can order pizza. Make it fast and easy to use." Everyone nods, and six weeks later you proudly demo a working ordering screen. The client frowns: there is no way to pay, it forgets the order if the phone loses signal, and it takes eight seconds to load on their aging tablet. Nobody lied, nobody was lazy — the team simply built the wrong thing, because "fast and easy" was never pinned down into anything you could actually build or check.

Requirements engineering is the discipline of turning fuzzy wishes like that into a precise, agreed statement of what the software must do — before you write the code. It is the first real activity of the software lifecycle, and it is where the most expensive mistakes are made. A famous rule of thumb: a requirements error caught in a conversation costs almost nothing to fix; the same error caught after release can cost a hundred times more, because by then it is baked into design, code, tests and documentation. Getting the requirements right is the single highest-leverage thing a team does.

Two kinds of requirement: functional and non-functional

Every requirement answers one of two very different questions, and mixing them up is a classic beginner mistake. Keep them in separate columns in your head:

The usual way to remember it: functional requirements are behaviours; non-functional requirements are the -ilities — performance, security, usability, reliability, scalability, maintainability, portability. The diagram below sorts a handful of real requirements into the two families.

Worked example: from a vague wish to crisp requirements

Requirements engineering is mostly the craft of sharpening. You start with what the customer actually said and rewrite it until it is something a developer can build and a tester can check. Watch a single vague sentence get pulled apart:

The customer says: "I want people to be able to log in securely and it shouldn't be slow."

Pull the functional behaviours out first — the things the system must do:

Now the non-functional qualities — "securely" and "not slow" made measurable:

Notice how much hid inside one polite sentence. "Securely" became a storage rule and a lockout behaviour; "not slow" became a number you can put a stopwatch to. The vague wish could never be tested; every line above can.

Because an adjective cannot fail a test. "The app shall be fast" can never be marked pass or fail — one person's "fast" is another's "sluggish", so at the end of the project everyone argues. "The search results shall appear within 500 ms for 95% of queries" can be measured, automated, and signed off. The whole art of a good non-functional requirement is replacing an opinion word (fast, secure, easy, robust) with a measurable target: a time, a count, a percentage, a named standard. If you cannot imagine the test, you have not finished writing the requirement.

User stories: requirements in the user's voice

A formal "The system shall…" list is precise but cold — it says what to build while losing sight of who wants it and why. Agile teams capture functional requirements as user stories instead: one short sentence in a fixed shape that always keeps the human in view.

As a <role>, I want <goal>, so that <benefit>.

For our pizza app:

As a hungry customer, I want to save my favourite order, so that I can re-order it in one tap next time.

The three parts each earn their place. The role reminds you a real kind of person wants this. The goal is the feature. The benefit ("so that…") is the part beginners drop — and it is the most valuable, because it tells you why, which lets you judge whether the feature is even worth building, or whether a simpler thing would give the same benefit.

A story on its own is only a promise of a conversation. What makes it buildable is its acceptance criteria: a checklist of concrete conditions that must all be true before the story counts as "done". They are how you turn "I want to save my favourite order" into something a tester can verify:

Acceptance criteria for "save my favourite order": - A logged-in customer can mark any past order as a favourite. - The saved favourite appears at the top of the home screen. - Tapping it re-creates the exact same basket of items. - A guest (not logged in) is prompted to sign in first.

Acceptance criteria are the bridge from a friendly story back to the hard-edged, testable precision of a "shall" requirement — the best of both worlds.

Checking acceptance criteria in code

Acceptance criteria are not just prose — they map almost directly onto code you can run. Below, a user story and its criteria are modelled as data, and a tiny checker runs a candidate feature against every criterion, reporting exactly which ones pass and which fail. This is the same shape as an automated acceptance test.

interface UserStory { role: string; goal: string; benefit: string; } interface AcceptanceCriterion { description: string; check: (order: Order) => boolean; } interface Order { isFavourite: boolean; shownOnHome: boolean; itemCount: number; loggedIn: boolean; } const story: UserStory = { role: "hungry customer", goal: "save my favourite order", benefit: "re-order it in one tap next time", }; const criteria: AcceptanceCriterion[] = [ { description: "logged-in customer can favourite an order", check: (o) => o.loggedIn && o.isFavourite }, { description: "favourite appears on the home screen", check: (o) => o.shownOnHome }, { description: "re-order restores the same basket of items", check: (o) => o.itemCount > 0 }, ]; // A candidate feature produces this order state: const candidate: Order = { loggedIn: true, isFavourite: true, shownOnHome: true, itemCount: 3, }; console.log(`Story: As a ${story.role}, I want to ${story.goal},`); console.log(` so that I can ${story.benefit}.`); console.log(""); let passed = 0; for (const c of criteria) { const ok = c.check(candidate); if (ok) passed++; console.log(` [${ok ? "PASS" : "FAIL"}] ${c.description}`); } const done = passed === criteria.length; console.log(""); console.log(`${passed}/${criteria.length} criteria met -> story is ${done ? "DONE" : "NOT done"}.`);

Try breaking a criterion: set loggedIn to false or itemCount to 0 and run it again. The story immediately reports "NOT done" and points at the exact criterion that failed — which is precisely why we make requirements testable in the first place.

What makes a requirement good?

Not all requirements are created equal. A professional requirement earns its keep by being:

A tempting mistake is to treat non-functional requirements as optional extras — "nice-to-haves" you will bolt on later once the "real" features work. This is backwards. Non-functional requirements are often the ones that kill a product: a banking app that leaks passwords, a checkout that takes fifteen seconds, a site that collapses under launch-day traffic. Every one of those has all its features working perfectly and is still a failure. Worse, qualities like security, performance and scalability are woven deep into the architecture — they are extremely expensive to "add later" because retrofitting them can mean rebuilding the whole system. NFRs are not the garnish; they are load-bearing. Gather and agree them alongside the functional requirements, from day one.

How do you actually gather requirements?

Requirements don't arrive gift-wrapped; you have to elicit them, and stakeholders are famously bad at telling you what they need directly. The main techniques:

Good teams triangulate: use several techniques, because each catches what the others miss. And elicitation never really stops — as stakeholders see the product take shape, they understand their own needs better, and the requirements evolve. That is normal, not a failure.

Because knowing what you want and being able to state it precisely to a stranger are totally different skills. Stakeholders forget the "obvious" rules they follow every day; they disagree with each other; they ask for solutions ("add a dropdown") instead of describing the underlying need ("I need to pick from last year's suppliers"); and they genuinely can't picture what's possible until they see it. There's a well-worn industry study claiming that incomplete or shifting requirements are among the top reasons software projects fail — more than any purely technical cause. That's the whole reason requirements engineering exists as a discipline: coaxing out the truth is a craft in itself, not a form to fill in.

Why this matters

You can write flawless, elegant, blazing-fast code and still deliver a total failure — if it does the wrong thing. Requirements engineering is the discipline that stops that from happening: it turns a client's fuzzy wish into an agreed, precise, testable statement of what to build, separates the what (functional) from the how well (non-functional), keeps the human in view with user stories, and pins "done" down with acceptance criteria. It is unglamorous, it is mostly talking and writing rather than coding — and it is the difference between building the thing right and building the right thing.