Bad user stories waste more engineering time than any other single artifact in product development. They cause misalignment, rework, and the classic "well, technically we built what the ticket said" outcome that leaves both PM and engineers frustrated.
Good user stories do the opposite: they focus everyone on the same outcome, remove ambiguity about "done," and give the team a clear artifact to test against.
Here's the format I use across every project I ship — with real examples of good and bad stories side by side.
The user story format that works
Template:
As a [specific user type],
I want to [specific action or capability],
so that [specific outcome or value].
Example of BAD story: > "As a user, I want to see my data, so that I can use the app."
- "user" is too generic (everyone is a user)
- "see my data" is too vague (which data? in what form?)
- "use the app" doesn't describe value (what value?)
Example of GOOD story: > "As a paying customer with more than 30 days of activity, I want to see a summary of my usage from the last 30 days on the dashboard, so that I can decide whether to upgrade to the next plan."
- Specific user type (paying, >30 days activity)
- Specific action (see summary of last 30 days on dashboard)
- Specific outcome (informed upgrade decision)
The test: if you can't describe WHO wants this, WHAT specifically they can do, and WHY it matters, the story isn't ready.
Why "As a user" is almost always wrong
"As a user" is a red flag. It usually means one of three things:
1. You haven't defined your user types. Real products have distinct users: admins, buyers, viewers, power users, first-timers, etc. Each has different needs. 2. You haven't identified who this specifically serves. If literally everyone benefits equally, the feature is probably too generic to be useful. 3. You're avoiding the harder question: "which segment does this move the needle for?"
Fix: define 3-6 user personas for your product. Every user story should specify which persona it's for.
Common persona archetypes:
- New user (day 1-7)
- Active user (>7 days, using core feature weekly)
- Power user (using advanced features, ready for tier upgrade)
- At-risk user (haven't logged in for 14+ days)
- Admin/owner (setup, configuration, billing)
- Team member (invited by owner, limited permissions)
The 3-part acceptance criteria format
A user story tells you WHAT to build. Acceptance criteria tell you when it's DONE.
Format (Gherkin/BDD-style):
Given [context/precondition],
When [action],
Then [expected outcome].
Example — Simple:
Given a paying user is on the dashboard,
When they view the "Usage" section,
Then they see: monthly API calls (with percentage of limit),
active integrations count, and days remaining in current billing period.
Example — With multiple criteria:
Story: "As a paying customer, I want to upgrade my plan from Pro to Enterprise directly from the billing page, so that I don't have to contact sales."
Acceptance criteria:
AC1: Given a Pro-tier user on /billing,
When they click "Upgrade to Enterprise",
Then they see a summary of the difference (features + price).
AC2: Given the upgrade summary is shown,
When the user clicks "Confirm upgrade",
Then Stripe charges the prorated difference immediately,
the account tier changes to Enterprise within 30 seconds,
and the user sees a success message + updated feature list.
AC3: Given the upgrade fails (payment declined),
When the failure is detected,
Then the user tier remains Pro,
they see a specific error message with the reason (declined, insufficient funds, etc.),
and no charge is created.
AC4: Given a user is on a monthly billing cycle,
When they upgrade mid-cycle,
Then the prorated amount is calculated based on remaining days
and the new cycle continues from the same billing date.
Notice: each AC is testable, independent, and specific. QA can write test cases directly from these.
The 6 traits of great user stories
1. Independent. Can be built and shipped without depending on other stories being done first.
2. Negotiable. The details can be discussed and refined during grooming. The story isn't a contract — it's a starting point.
3. Valuable. Delivers user or business value on its own. If it doesn't move any metric, why are you building it?
4. Estimable. The team can estimate effort. If they can't, the story is too vague — break it down.
5. Small. Fits in a single sprint (usually 1-8 story points). If bigger, split it.
6. Testable. Has clear acceptance criteria that QA (or automated tests) can verify.
Mnemonic: INVEST.
How to slice large stories
If a story is too big, split it. But split it correctly.
Bad split: by layer.
- Story 1: "Build the backend for upgrade flow"
- Story 2: "Build the frontend for upgrade flow"
- Story 3: "QA the upgrade flow"
Problem: none of these deliver value on their own. Backend without frontend is useless. QA without built features is impossible.
Good split: by scenario or user segment.
- Story 1: "Upgrade from Pro to Enterprise (annual subscription)"
- Story 2: "Upgrade from Pro to Enterprise (monthly subscription with prorating)"
- Story 3: "Downgrade from Enterprise to Pro (at end of billing period)"
- Story 4: "Handle failed upgrade payments (retry + user notification)"
Each of these is independently valuable AND fits in a sprint.
Splitting patterns:
- By user segment (upgrade for annual customers first, monthly second)
- By workflow step (create → edit → delete as separate stories)
- By happy path vs edge cases (build happy path first, add error handling as separate stories)
- By platform (web first, mobile second)
- By data (read before write, single item before bulk)
Real examples: transforming bad stories
Example 1: Vague feature request
Bad: "Add search functionality to the app."
What's wrong: search for what? By whom? What's a successful search? How does it handle no results?
Fixed:
Story: "As a support agent handling a customer inquiry, I want to search customers by email, phone, or account ID, so that I can pull up the correct customer within 5 seconds."
Acceptance criteria:
AC1: Given a support agent is logged in,
When they type in the search bar,
Then results appear as they type (debounced 300ms).
AC2: Given they search by email,
When the email matches a customer,
Then that customer appears as the first result with account status shown.
AC3: Given no results match,
When 500ms has passed with no matches,
Then a "No customer found" message shows with a "Create ticket without customer" link.
AC4: Given multiple matches (e.g., partial email),
When results are shown,
Then up to 10 results appear sorted by most recent activity.
AC5: Given the search text is 3+ characters,
Then the search fires. Otherwise no search is made.
Example 2: Feature-focused instead of outcome-focused
Bad: "Add a dashboard chart showing sign-ups over time."
What's wrong: why? What decision does this enable? Who uses it?
Fixed:
Story: "As a marketing lead reviewing campaign performance, I want to see daily sign-ups from the past 30 days broken down by acquisition source (organic, paid, referral), so that I can decide where to increase budget."
Acceptance criteria:
AC1: Given a marketing user on /dashboard,
When the sign-ups chart loads,
Then the chart shows the last 30 days with daily granularity.
AC2: Given the chart is loaded,
When the user hovers a data point,
Then they see the exact count for that day + breakdown by source.
AC3: Given the user wants to compare periods,
When they select a comparison range (e.g., previous 30 days),
Then the previous period appears as a dashed line for comparison.
AC4: Given data is loading,
When the query takes longer than 500ms,
Then a skeleton loader shows. Once loaded, the actual chart replaces it.
AC5: Given no sign-ups in the period,
When the query returns 0 results,
Then the chart area shows "No sign-ups in this period" with a link to check the acquisition setup.
Example 3: Multi-persona request in one story
Bad: "As a user, I want notifications when things happen."
What's wrong: which user? What things? What kind of notification?
Fixed as multiple stories:
Story 1: "As a team owner, I want to receive an email when a team member removes an integration, so that I know about configuration changes I didn't initiate."
Story 2: "As a team member, I want to receive an in-app notification when I'm assigned a task, so that I don't miss work items."
Story 3: "As a billing admin, I want to receive an email 3 days before a card expires, so that I can update the payment method before service is interrupted."
Each has its own clear AC, priority, and testability.
Common mistakes
Mistake 1: Writing "requirements" not "stories"
Bad: "The system shall provide search functionality with results returned within 200ms."
Problem: this is a requirement, not a story. It doesn't say WHO wants it or WHY.
Fix: rewrite as a proper story. The 200ms latency becomes an AC, not the story itself.
Mistake 2: Acceptance criteria that describe implementation
Bad AC: "Given the user clicks search, Then the API endpoint /api/v1/search is called with the query parameter q=..."
Problem: this describes HOW to build it, not WHAT should happen. Locks implementation.
Fix: "Given the user clicks search, Then results relevant to their query appear within 500ms." Implementation is the engineer's call.
Mistake 3: Stories with 30 acceptance criteria
Problem: the story is too big. Split it.
Fix: if you have >10 ACs, the story should probably be 3-5 smaller stories.
Mistake 4: Skipping ACs for "simple" stories
Bad: "Add a delete button." (no ACs)
Problem: does delete confirm? Is it soft delete or hard? What about foreign keys?
Fix: even simple stories deserve 3-5 ACs covering happy path + edge cases.
Mistake 5: Writing stories in isolation
Problem: PM writes stories alone. Engineers see them for the first time in sprint planning. Confusion.
Fix: grooming sessions 3-5 days before sprint planning. PM presents drafts. Engineers ask questions. Refine together.
The grooming ritual
Before sprint planning, run a 60-90 min grooming session:
1. PM presents draft stories (10-15 min per story) 2. Engineers ask clarifying questions ("what happens if...?") 3. Team discusses edge cases and adds ACs 4. Team estimates (story points, T-shirt sizes, hours — whatever your team uses) 5. Team identifies open questions that block estimation
Output: stories that everyone understands, with confidence they can build.
Rule: never start a sprint with unrefined stories. That's how you get "we needed one more day" chronically.
The template I actually use
I keep a Notion / Linear template like this for every story:
## Story
As a [persona type],
I want to [action],
so that [outcome].
## Business context
[1-2 sentences: why this matters, what metric it moves]
## Acceptance criteria
AC1: [Given/When/Then]
AC2: [Given/When/Then]
AC3: [Given/When/Then]
...
## Out of scope
- [thing NOT included in this story]
- [thing punted to future story]
## Design
- [link to Figma or wireframe]
## Open questions
- [ ] [question needing answer before dev starts]
The "out of scope" section is underrated. It prevents scope creep during development ("wait, don't I need to handle X?"). If X isn't in the ACs and is explicitly out of scope, defer.
What to do this week
If your stories are vague:
- Take the top 5 stories in your backlog
- Rewrite them with the As/I want/So that format + at least 3 ACs each
- Run them past 2 engineers before sprint planning
If your team is misaligned:
- Add a grooming session 3 days before planning
- Include design, engineering, and QA
- Refine stories collaboratively
If stories are consistently too big:
- Apply INVEST — the S is where you're failing
- Split by scenario, not by layer
- Aim for stories that fit in 1-3 days of work
If QA finds bugs that weren't in ACs:
- ACs weren't specific enough
- Add negative cases (what should NOT happen) explicitly
Related reads:
- How to write a PRD — the doc that precedes user stories
- How to run a productive standup — stories drive standups; bad stories create pointless standups
- Sprint planning that actually works — planning is only as good as your stories
- How to delegate effectively — user stories are how you delegate build work
User stories aren't bureaucratic overhead. They're the tightest possible contract between product intent and engineering execution. Write them well and everything downstream gets faster.
---
If your stories consistently produce misaligned deliverables or "well, we built what the ticket said" surprises, reach out via the contact page. I'll audit a few and show you where the format breaks down.
