Writing Good Requirements: A Step-by-Step Guide
- Shawn West
- Mar 27
- 10 min read
Updated: Aug 16
Most requirements pass sign-off and then fail in the sprint review — when three people discover, too late, that they read the same sentence three different ways. The step-by-step form matters less than the one property underneath it: a good requirement is a sentence whose "done" you could argue about and settle before anyone writes code. Here is how to write for that property, with a worked rewrite and a test you can run on your own backlog this week.
A support-tooling team once shipped a requirement everyone in the room approved: "The system shall allow users to search orders." Product signed it. Engineering estimated it. QA filed it. It read like a requirement — subject, verb, object, the word "shall." Two weeks later the feature came back to sprint review and the room fell into an argument that had nothing to do with code quality and everything to do with that one sentence. The engineer had built full-text search over order notes. The tester had written a test that looked up an order by its ID. The product manager typed a customer's last name and got nothing — because none of the three had been building the same feature.
Nobody was wrong. The sentence was. It had cleared every checklist a requirement is supposed to clear and still carried three legal interpretations out the door, and the cost of discovering that arrived after the estimate, in the most expensive room to discover it. That gap between "looks like a requirement" and "is one" is the subject of this guide.
What makes a requirement good — and why the usual list falls short
Open any specification textbook and you get the list. A good requirement is necessary, unambiguous, testable, complete, consistent, and atomic — and those attributes are real; the international standard for requirements engineering, ISO/IEC/IEEE 29148, names nearly the same set. But the list describes the destination without telling you how to know you have arrived. "The system shall allow users to search orders" feels necessary, unambiguous, and atomic to the person who wrote it. Every bad requirement feels good to its author. That is why it shipped.
There is a sharper, more usable definition hiding underneath the list: a good requirement is one whose "done" you could argue about and settle before code. Not "a sentence everyone nods at" — a sentence where, if two competent people disagreed about whether the system met it, you could resolve it by reading the requirement, not by holding a meeting. Ambiguity, incompleteness, and non-atomicity are not separate virtues to check off; they are the three ways a sentence fails that one test. "Search orders" fails because the disagreement it produced — search by what, returning what, for whom — cannot be settled by rereading it.
Test you can run: take any requirement in your backlog and ask, could two reasonable people disagree about whether it is done? If yes, you have not written a bad requirement by accident — you have deferred a fight. The only question left is when you want that fight: now, at the cost of a ten-minute conversation, or in QA, at the cost of a rebuilt feature.
Testability is the master attribute, and it has a physical test
Of the six qualities, testability is the one to lead with, because the others follow from it. A sentence you cannot write a test for is a sentence whose "done" is undefined — which means it is also, necessarily, ambiguous and incomplete. Chase testability and the rest come along behind it.
Testability has a concrete, almost mechanical check, and it is the most useful thing in this guide: hand the sentence to two engineers who have not talked to each other, and ask each to write the test that would verify it. If they write the same test, the requirement is unambiguous — there was only one interpretation to find. If they write different tests, you have located the ambiguity precisely, at zero cost, before anyone spent a sprint on the wrong one. Run "search orders" through this and it fails instantly: one tests an ID lookup, another a name substring, a third full-text over notes. Three tests from one sentence is the signature of a requirement that will detonate later.
This is not a metaphor for rigor. It is something you can do in a review in five minutes, and it converts "this feels vague" — an opinion nobody has to accept — into "here are two passing tests for the same sentence," which nobody can.
Test you can run: for your next requirement, actually do it. Two people, independent, each writes the acceptance test. Diff the tests. The diff is the ambiguity, made visible and cheap.
Developed example: "users can search orders," rewritten
(Developed example — a simple scenario.)
Start where the team started, with the sentence that passed sign-off:
Draft: The system shall allow users to search orders.
Now run the two-engineer test and watch it come apart. Three questions have no answer in the sentence, and each is a real fork the build has to take:
Which users, over whose orders? A customer searching their own history and a support agent searching every order in the system are two different features with two different permission models. The sentence hides the harder one.
Search by what? Order ID, customer name, email, date range, free text in the notes? Each is a different index, query, and UI. "Search" named none of them.
What counts as a match, and what comes back? Exact or substring? Case-sensitive? Do cancelled orders appear? In what order?
Here is the moment the badness was catchable — and it was not sprint review. It was the requirements conversation that never happened. Every one of those three questions is a discovery question: a question about what "right" means that has to be answered before the feature is built, not discovered by building it. Asked at the whiteboard, they cost ten minutes and one Slack thread with the support lead. Asked implicitly, by three engineers each guessing an answer, they cost a sprint. The vagueness did not save time; it moved the cost downstream and multiplied it.
Take the answers the discovery questions surface — support agents need to find any customer's orders by name, email, or order ID; results are newest-first; cancelled orders are included and labelled — and the sentence rewrites itself into something with a settleable "done":
REQ-014: The system shall allow users with the support agent role to search all orders by customer name, customer email, or order ID. REQ-015: The system shall match customer name and email as case-insensitive substrings, and order ID as an exact match. REQ-016: The system shall return matching orders newest-first, including cancelled orders, each labelled with its status. REQ-017: The system shall restrict order search to support agents; customers shall not have access to cross-account search.
Four sentences where there was one. Each survives the two-engineer test — hand REQ-015 to any two people and they write the same test, because there is only one way to read "case-insensitive substring" and "exact match." And notice what the rewrite is not: it never says Elasticsearch, never dictates the query. It pins down what right means and leaves how to the engineers — behaviour fixed, implementation free. The acceptance criteria that turn each of these into concrete pass/fail conditions are their own craft, covered in acceptance criteria done right.
Test you can run: take your vaguest current requirement and list the questions a builder would have to answer to start. If any decides what the feature is rather than how it is built, it belongs in a requirements conversation this week — not in a pull request three weeks from now.
The discovery questions that force the vagueness out
The rewrite above was not clever wordsmithing. It was the output of a fixed set of questions — the reusable method you can run on any requirement, in any domain:
Who is the actor, named by role and permission, not the generic "user"? ("Users" almost always hides the distinction that matters — here, agent versus customer.)
What action, stated as a specific verb rather than a placeholder like "manage" or "handle"?
On which data, bounded — which records are in scope, which excluded?
Under what conditions, including the edge cases at the boundary?
Verified how — what is the observable "done"?
These are discovery questions, and calling them that is the whole point. "Search orders" shipped broken not because the author wrote a bad sentence, but because the author had not yet discovered the answers, and the sentence papered over that gap. This is the failure ShiftQuality keeps pointing at: projects fail in discovery, not in code. Three engineers wrote perfectly good code. It was a discovery failure wearing the costume of a requirement — the questions that define "right" were never asked, so the code had nothing true to be built against.
Which is why writing good requirements is not really a writing skill — it is an elicitation skill. If you cannot answer question 3 for your own requirement, the fix is not a better sentence but a conversation with whoever knows which orders are in scope, and there are repeatable techniques for getting those answers out of stakeholders. When the discovery is done, the sentence is nearly automatic; when it is skipped, no amount of "shall" will save it.
One caution the questions expose: they surface functional behaviour readily and stay silent on the qualities that decide whether users tolerate the feature — how fast search returns, how it behaves under load, what happens when the index is stale. Those non-functional requirements are the quiet ones, and they need their own deliberate pass, because no actor-action-object question prompts you to ask "how fast?"
Test you can run: for your requirement, fill in who / does what / to which data / under what condition / verified how. Every blank is not a gap in the sentence — it is a discovery conversation you have not had yet. Book it before you estimate the work.
How much precision is right — and when less is correct
Precision is not a virtue you maximize. It is a dial, and both ends are failures. Push too far toward detail and you stop writing requirements and start writing code in prose — dictating the database, the framework, the layout — which strips the team of the design freedom that is their actual job. Leave it too loose and you get "search orders." The target is the middle: pin the what, free the how.
Requirement precision | Testability | Room to design | Review & maintenance cost |
Too vague — "allow users to search orders" | None; every reader writes a different test | Unbounded, because nothing is decided — the team designs the feature, not just the build | Low to write, ruinous later; the cost reappears as rework in QA and sprint review |
Right-sized — actor, action, bounded data, observable "done"; implementation left open | High; two engineers write the same test | Correct amount; what is fixed, how is the team's to choose | Moderate and paid once, up front, where it is cheapest |
Over-specified — dictates the index, the framework, the exact UI | High but brittle; tests break when a valid implementation changes | None; the requirement has pre-made design decisions it had no business making | High; every implementation detail is now a sentence to review, version, and keep true |
The exception that proves the dial exists: when you are still in discovery, a loose requirement is the correct requirement. For a spike or a genuinely uncertain product direction, "let users explore their order history — we do not yet know which access patterns matter" is honest; pinning it prematurely would be false precision, a "done" for a feature whose shape you are still learning. The rule is to say so out loud — mark it a discovery item, not a build item, so no one estimates it as though the answers exist. The failure is not looseness; it is looseness disguised as a settled requirement, which is exactly what "search orders" was.
Test you can run: read your requirement and name one implementation it forbids. If it forbids nothing — any build satisfies it — it is too vague. If what it forbids is a technology choice rather than a wrong behaviour, it is over-specified. Right-sized requirements forbid wrong outcomes, not valid designs.
The common mistakes, and the one-second tell for each
Most bad requirements are one of a few recurring shapes, each with a tell you can spot without judgement:
Implementation smuggled in — "The system shall use a Redis cache." Tell: it names a technology. Requirements describe behaviour; naming the tool means you wrote a design decision and disguised it as a need — and the behaviour it was trying to express (a latency target, say) went unstated.
The multi-part requirement — "The system shall let users sign in and view their profile and edit settings." Tell: count the "and"s joining independent verbs. Three verbs is three requirements; when one fails a test you cannot say which, and you cannot trace or change them independently.
Aspirational language — "The system should ideally be user-friendly." Tell: "should" or "ideally" where "shall" belongs, or an adjective with no measure ("fast," "robust," "easy"). Each is a future argument — make it testable or move it to a wish list.
Compound conditions — "shall do X when Y or Z unless A and B." Tell: you cannot hold the logic in your head on one read. Decompose into cases until each is followable.
These tells are fast checks, but they share a source. The distinction between what the business needs and how the system delivers it is the line most of these mistakes cross — a skill of its own, worth understanding as the difference between business, user, and system requirements.
Test you can run: scan your requirement for a technology name, count its independent verbs, and highlight every adjective without a number. Three fast passes; each catches a different one of these before review does.
What to do next
Do not rewrite your whole requirements document. Do this instead, this week: pick the five requirements that would be most expensive to get wrong — the ones tied to money, permissions, or data you cannot un-corrupt — and run the two-engineer test on each. Hand them to two people, have each write the verifying test, and diff. Every diff is an ambiguity you just caught for the price of a conversation instead of a sprint.
That is the whole shift, and it is a shift in when, not how much. Writing good requirements is not about writing more, or more formally — it is about moving the argument about "done" to the cheapest place to have it: a whiteboard before the estimate, not a sprint review after the build. A requirement is finished not when it sounds like one, but when you have already had, and settled, every disagreement it could produce.
Sources
ISO/IEC/IEEE 29148:2018, Systems and software engineering — Life cycle processes — Requirements engineering — characteristics of individual requirements (necessary, unambiguous, complete, singular, feasible, verifiable).
IEEE Std 830-1998, Recommended Practice for Software Requirements Specifications — quality attributes of a good specification (unambiguous, complete, verifiable, consistent, traceable).


