Why this matters: this is the single cheapest fix available to a struggling team. Acceptance criteria are what end the argument at the demo, and they are where your test cases come from. If I could only change one thing about how we work, it would be this one. That claim is argued in Why We Miss Deadlines.
What they are
Acceptance criteria are the specific, checkable conditions a single User Story must satisfy to be accepted. They belong to that one Story and nothing else.
They answer exactly one question:
How will we know this specific Story works correctly?
Not “is the code good” and not “did we follow our process”. Those are Definition of Done. Acceptance criteria are only about whether this behaviour is right.
What happens without them
Without written criteria the expected behaviour lives in somebody’s memory of a conversation. Then the demo happens:
Stakeholder: “That is not what I expected.”
Developer: “That is exactly what we discussed.”
Both of them believe they are right, and both of them probably are, because two people left the same conversation with different pictures in their heads. There is no artifact to check against, so the loudest or most senior person wins, the Story reopens, the sprint overflows, and everyone quietly loses a little trust in the process.
Written criteria do not make stakeholders agree. They make disagreement happen early, on a document, before the code is written, when changing your mind is free.
Two formats, both fine
Given / When / Then
Borrowed from BDD. Best for behaviour that depends on state.
Given a registered user with a verified email
When they request a password reset
Then a reset email is sent to that address
And the reset link is valid for 30 minutes
The three parts are: Given the starting state, When the action, Then the observable outcome. The discipline of filling all three is what forces you to think about preconditions, which is where most missed edge cases hide.
Checklist
Best for rules, constraints and validation, where a full Given/When/Then would be noise.
- A used reset token cannot be reused.
- The response is identical whether or not the email exists in our system.
- Password must be at least 8 characters and not one of the last 3 used.
- Rate limit: maximum 5 reset requests per email per hour.
Mix both in one Story. Use Given/When/Then for the main flows, a checklist for the rules. Nobody gets points for format purity.
What makes a criterion good
It is observable from outside. “The service layer validates input” is not testable by a tester. “Submitting a negative quantity shows the error ‘Quantity must be greater than zero’ and nothing is saved” is.
It is binary. Pass or fail, no judgement call. “The page loads fast” is an argument waiting to happen. “The list of 1,000 invoices renders within 2 seconds on a normal connection” is a test.
It describes behaviour, not implementation. “Store the token in Redis with a TTL” is a design decision that belongs to developers. “The link stops working after 30 minutes” is the behaviour you actually care about. Write the second, let the team choose the first.
It includes the unhappy paths. This is where teams are weakest. For every criterion describing success, ask: what if it is empty, too long, duplicated, already deleted, in a different currency, in a different timezone, submitted twice, or attempted by a user without permission? Most production bugs live in exactly those questions, and most of them cost nothing to think of in advance.
It says what is out of scope. An explicit “not in this Story” line prevents the demo from turning into a scope negotiation. This is the cheapest scope control device there is, and it is why Feedback and Scope Control leans on it.
How many is right
Roughly three to eight. Fewer than three usually means you have not thought about edge cases. More than about eight is a strong signal the Story is too big and should be split using the patterns in Writing User Stories.
Do not write a hundred criteria describing every field validation in a giant form. Group them: “all mandatory fields show a field level error when empty” plus a table of the fields is one criterion, not fifteen.
Who writes them and when
The Product Owner owns them, but they must not be written alone in a room. The good version is written during refinement with a developer and a tester present, because each role spots different gaps. That is the Three Amigos idea from Writing User Stories.
Timing matters: before development starts, not after. Microsoft’s own guidance for Azure DevOps says to define acceptance criteria before implementation so the team and stakeholders share the same expectation. Criteria written after the code are not criteria, they are a description of whatever got built.
Having criteria is also one of the required items in Definition of Ready. That is the mechanism that stops undefined Stories entering a sprint.
They are your test cases
This is the connection that fixes the “we have features but no test cases” problem.
Every acceptance criterion maps to at least one test case. If a Story has six criteria, that Story generates six test cases minimum, and they were effectively written during refinement, before coding, at zero extra cost. The tester’s job becomes turning them into executable tests and adding the deeper edge cases, rather than reverse engineering intent from a finished screen.
So the chain is:
flowchart LR
A["Story"] --> B["Acceptance Criteria"]
B --> C["Test Cases"]
C --> D["Regression suite"]
D --> E["Living documentation"]
Skip the second box and every box after it is missing too. That is the entire mechanism behind our missing coverage, and the full version is in Test Case Strategy. The same written criteria are what later make Feature Inventory and Ownership possible, because in eight months the criteria are the only honest record of what the feature was supposed to do.
Acceptance Criteria versus Definition of Done
Constantly confused, completely different.
| Acceptance Criteria | Definition of Done | |
|---|---|---|
| Scope | One Story | Every Story, always |
| Question | Is this behaviour correct? | Is the work professionally complete? |
| Written by | PO with dev and tester, per Story | The whole team, once, improved over time |
| Example | “Reset link expires after 30 minutes” | “Code reviewed, tests pass, no critical defects, docs updated” |
| Changes | Every Story | Rarely |
A Story can satisfy every acceptance criterion and still not be Done, because nobody reviewed the code or wrote the tests. And a Story can satisfy your entire Definition of Done checklist while doing the wrong thing, because the criteria were wrong. You need both.
Worked examples
Password reset
Story: As a user who forgot my password, I want to reset it by email, so that I can get back into my account without contacting support.
1. Given a registered email, when reset is requested, then an email with a unique link is sent within 1 minute.
2. Given an unregistered email, then the on screen response is identical to case 1 and no email is sent.
(Reason: we must not reveal whether an account exists.)
3. The reset link expires 30 minutes after it is generated.
4. A link that has already been used cannot be used again.
5. The new password must satisfy the password policy and cannot match the previous password.
6. On successful reset, all existing sessions for that user are invalidated.
7. Maximum 5 reset requests per email per hour, after which further requests are silently ignored.
Out of scope: reset by SMS, admin initiated reset, password expiry policy.
Criterion 2 and criterion 6 are the interesting ones. Neither is obvious, both are security relevant, and neither would have appeared if the Story went straight from a one line request into development. That is what fifteen minutes of refinement buys.
Invoice PDF export
Story: As an accountant, I want to export an invoice as PDF, so that I can email it to a client who does not use the portal.
1. Given a saved invoice, when I click Export PDF, then a PDF downloads named INV-<number>.pdf.
2. The PDF shows: company logo, company address, client name and address, invoice number,
invoice date, due date, line items with quantity, rate, tax and amount, subtotal, tax total,
grand total, and payment terms.
3. Amounts display in the invoice's currency with the correct symbol and 2 decimal places.
4. An invoice with more than 20 line items paginates, repeating the header row on each page.
5. A draft invoice exports with a visible DRAFT watermark.
6. A cancelled invoice cannot be exported and the button is disabled with a tooltip explaining why.
Out of scope: Excel export, CSV export, emailing the PDF directly, bulk export of many invoices.
Now, when the stakeholder sees the demo and asks for Excel and email, there is no debate about whether it was “supposed to be included”. It is written down as out of scope, it becomes new backlog items, and the current Story gets to finish. That is exactly the move described in Feedback and Scope Control, and it is the difference between a 5 point Story that finishes and a 5 point Story that silently becomes 20.
A technical enabler
Even enabler work can have criteria, and it should.
Story: Move reporting queries to a read replica so that heavy reports stop slowing down invoice entry.
1. All read only report queries execute against the replica.
2. Write operations continue to use the primary.
3. If the replica is unreachable, reports fall back to the primary and a warning is logged.
4. Replica lag above 10 seconds is visible on the monitoring dashboard.
5. Invoice save time under a concurrent heavy report load stays under 800ms at the 95th percentile.
Criterion 5 is the whole reason the Story exists, and writing it as a number is what makes the work verifiable rather than a matter of opinion. That number is also a natural service level indicator, which is where SLI SLO and Error Budget picks up.
Common mistakes
- Writing them after the code. Then they just describe what was built, including the bugs.
- Restating the Story. “The user can export a PDF” adds nothing. Criteria must add detail the Story does not have.
- Only happy paths. If there are no error cases listed, they were not considered, and they will appear as production bugs.
- Hidden design decisions. Specifying tables, endpoints and libraries removes the team’s ability to find a better solution.
- Vague words. “Fast”, “user friendly”, “properly”, “as discussed”. Every one of these is a future argument. Replace with a number or an example.
- Treating them as fixed forever. If refinement or development reveals a criterion is wrong, change it deliberately and tell the Product Owner. Changing criteria is fine. Changing them silently is not.