Skip to main content

User Story Guidelines

User stories shift focus from writing detailed requirements to having conversations about what users need. A well-written user story captures the essence of a requirement in a way that facilitates discussion, estimation, and implementation.

User stories are not complete specifications - they are placeholders for conversation. The goal is to capture enough detail to discuss, estimate, and implement, not to eliminate all questions upfront.


The INVEST Principle

The INVEST acronym provides quality criteria for effective user stories. Each story should be:

Independent

Stories should be self-contained and deliverable without depending on other stories being completed first. Independence reduces coordination overhead and allows flexible prioritization.

Why it matters: Dependencies create bottlenecks. If Story B depends on Story A, you can't work on B until A is done, limiting parallelization and forcing strict ordering.

How to achieve it:

  • Avoid technical dependencies (e.g., "create API" as separate story from "create UI")
  • Combine tightly coupled work into a single story
  • If dependencies are unavoidable, make them explicit and prioritize accordingly

Example:

 Bad (Dependent):
Story 1: Create payment database table
Story 2: Create payment API endpoints (depends on Story 1)
Story 3: Create payment UI (depends on Story 2)

Good (Independent):
Story 1: As a customer, I want to initiate a payment so I can transfer money
(Includes database, API, and UI as a complete vertical slice)

Negotiable

Details are negotiable until the story is being implemented. The story is not a contract but rather an invitation to collaborate on the best solution.

Why it matters: Requirements evolve as we learn. Locking in implementation details too early prevents discovering better solutions.

How to achieve it:

  • Focus on the "what" and "why", not the "how"
  • Avoid specifying implementation details in the story
  • Leave room for technical decisions during implementation
  • Welcome alternative approaches during refinement

Example:

 Bad (Not Negotiable):
As a developer, I want to implement a Redis cache with 5-minute TTL
using Spring Cache annotations so that API performance improves.

Good (Negotiable):
As a user, I want payment history to load quickly (< 1 second)
so that I can review my transactions without waiting.

(How caching is implemented - Redis, local cache, database optimization -
is negotiable and decided during technical design)

Valuable

Every story must deliver value to a user, customer, or stakeholder. If it doesn't provide clear value, it's probably a task, not a story.

Why it matters: Value-driven work ensures the team focuses on outcomes, not outputs. Technical work without clear value is often unnecessary complexity.

How to achieve it:

  • Write from user perspective, not developer perspective
  • Articulate the benefit in the "so that" clause
  • Ask "Why does this matter?" to uncover real value
  • If no clear value exists, question whether the work is needed

Example:

 Bad (No Clear Value):
As a developer, I want to refactor the payment service
so that the code is cleaner.

Good (Clear Value):
As a customer service representative, I want payment failures
to show detailed error messages so that I can help customers
resolve issues without escalating to engineering.

Estimable

The team must be able to estimate the size/effort of the story with reasonable confidence.

Why it matters: Estimation drives sprint planning and capacity management. If a story can't be estimated, it's too vague or too complex.

How to achieve it:

  • Provide enough detail to understand scope
  • Clarify acceptance criteria
  • Identify unknowns and create spikes if needed
  • Break down stories that are too large or complex

Example:

 Bad (Not Estimable):
As a user, I want better payment performance.

(Too vague - what does "better" mean? What part of payments? How much improvement?)

Good (Estimable):
As a user, I want payment initiation API to respond in < 500ms (P95)
so that the payment form feels responsive.

Acceptance Criteria:
- GET /api/payments/{id} responds < 500ms at P95
- POST /api/payments responds < 500ms at P95
- Measured under load of 1000 concurrent users

Small

Stories should be small enough to complete within a sprint. Large stories are called "epics" and must be broken down.

Why it matters: Small stories are easier to understand, estimate, implement, test, and review. They reduce work-in-progress and accelerate feedback cycles.

How to achieve it:

  • Target 1-5 story points (or 1-3 days of work)
  • If larger, split along user journeys or acceptance criteria
  • Vertical slices (full stack) are better than horizontal slices (layers)

Example:

 Bad (Too Large - Epic):
As a user, I want a complete payment management system with history,
refunds, recurring payments, and fraud detection.

Good (Small, Vertical Slices):
Story 1: As a user, I want to view my payment history
Story 2: As a user, I want to request a refund for a payment
Story 3: As a user, I want to set up recurring payments
Story 4: As a user, I want to be alerted if a payment looks suspicious

Testable

Stories must have clear acceptance criteria that can be verified through testing.

Why it matters: If you can't test it, you can't confirm it's done. Testability ensures shared understanding of "done".

How to achieve it:

  • Define explicit acceptance criteria
  • Use Given-When-Then format for clarity
  • Include edge cases and error scenarios
  • Specify success metrics

Example:

 Bad (Not Testable):
As a user, I want payments to work well.

Good (Testable):
As a user, I want to initiate a payment so that I can transfer money.

Acceptance Criteria:
- Given I have sufficient balance
When I submit a valid payment
Then the payment status shows "COMPLETED"
And my balance decreases by the payment amount

- Given I have insufficient balance
When I submit a payment
Then I see error "Insufficient funds"
And no money is transferred

- Given I enter an invalid recipient ID
When I submit a payment
Then I see error "Recipient not found"

User Story Template

Basic Format

As a <user persona>,
I want <goal/capability>
So that <benefit/value>.

Anatomy Breakdown

As a <user persona>:

  • Who wants this feature? Be specific.
  • Use actual personas, not roles like "developer" or "system"
  • Examples: "bank customer", "account manager", "payment operator", "system administrator"

I want <goal/capability>:

  • What does the user want to do? Focus on the capability, not implementation.
  • Describe the user's intention, not technical details
  • Keep it concise (one sentence)

So that <benefit/value>:

  • Why do they want it? What value does it provide?
  • Articulate the business or user benefit
  • This is the most important part - it justifies the work

Extended Template

**Story**: <Short descriptive title>

**As a** <user persona>
**I want** <goal/capability>
**So that** <benefit/value>

**Acceptance Criteria**:

1. **Given** <precondition>
**When** <action>
**Then** <expected result>

2. **Given** <precondition>
**When** <action>
**Then** <expected result>

**Additional Context**:
- <Any relevant background, constraints, or clarifications>

**Dependencies**:
- <Links to other stories, external dependencies>

**Definition of Done**:
- <Any story-specific DoD items beyond standard team DoD>

Writing Effective Acceptance Criteria

Acceptance criteria define the boundaries of the story and specify what "done" means. They form the basis for testing and verification.

Given-When-Then Format

This format, borrowed from Behavior-Driven Development (BDD), creates testable scenarios:

Given <initial context/state>
When <action/event occurs>
Then <expected outcome/result>

Benefits:

  • Forces concrete scenarios
  • Aligns with test automation (Cucumber, JUnit)
  • Removes ambiguity
  • Facilitates conversation

Example:

Story: As a customer, I want to view my payment history

Acceptance Criteria:

1. Given I am a logged-in customer with payment history
When I navigate to the payment history page
Then I see my 10 most recent payments listed
And each payment shows: date, recipient, amount, status

2. Given I am a logged-in customer with no payment history
When I navigate to the payment history page
Then I see message "No payments found"

3. Given I am a logged-in customer with more than 10 payments
When I scroll to the bottom of the payment list
Then I see a "Load More" button
When I click "Load More"
Then 10 more payments are displayed

4. Given I am not logged in
When I try to access payment history
Then I am redirected to the login page

Checklist Format

For stories with multiple requirements that don't follow a scenario pattern:

Story: As a developer, I want automated deployment to staging

Acceptance Criteria:

- [ ] Pipeline automatically deploys to staging after merge to develop
- [ ] Deployment includes database migrations
- [ ] Deployment uses zero-downtime strategy (blue-green)
- [ ] Deployment notification sent to #deployments Slack channel
- [ ] Rollback capability available via GitLab UI
- [ ] Staging environment matches production configuration

Edge Cases and Error Scenarios

Don't forget unhappy paths:

Story: As a user, I want to initiate a payment

Acceptance Criteria:

Happy Path:
- Given valid payment details
When I submit payment
Then payment status is "COMPLETED"

Edge Cases:
- Given payment amount is exactly $0.01
When I submit payment
Then payment succeeds (boundary test)

- Given payment amount has > 2 decimal places ($10.567)
When I submit payment
Then amount is rounded to $10.57

Error Scenarios:
- Given insufficient account balance
When I submit payment
Then I see error "Insufficient funds"
And payment status is "FAILED"

- Given network timeout during processing
When payment is submitted
Then payment shows "PENDING" status
And retry mechanism attempts payment again

Common User Story Patterns

Feature Story

**Story**: View Payment History

**As a** bank customer
**I want to** view my recent payment transactions
**So that** I can track my spending and verify payments went through

**Acceptance Criteria**:

1. Given I am logged in
When I navigate to payment history
Then I see my 10 most recent payments

2. And each payment shows:
- Payment date
- Recipient name
- Amount
- Status (completed, pending, failed)

3. Given I have more than 10 payments
When I scroll to bottom
Then I see "Load More" button

Technical Story (Spike)

**Spike**: Investigate payment retry mechanisms

**As a** development team
**We want to** research retry strategies for failed payments
**So that** we can make an informed decision on implementation approach

**Acceptance Criteria**:

- Research 3 retry approaches (Spring Retry, Resilience4j, custom)
- Document pros/cons of each approach
- Provide recommendation with justification
- Create follow-up implementation story

**Timebox**: 4 hours
**Output**: Technical design document in Confluence

Bug Fix Story

**Story**: Fix duplicate payment processing

**As a** customer
**I want** payments to be processed exactly once
**So that** I'm not charged multiple times for the same payment

**Bug Description**:
Users report being charged twice when clicking "Pay" multiple times
quickly. Root cause: No idempotency check on payment endpoint.

**Acceptance Criteria**:

1. Given I submit a payment request
When I submit the same payment again (same idempotency key)
Then the second request returns the original payment result
And no duplicate charge occurs

2. Given I submit a payment
When the payment is still processing
And I submit again with same idempotency key
Then I receive HTTP 409 Conflict
And message "Payment already in progress"

**Root Cause**: Missing idempotency key validation
**Fix**: Add idempotency middleware using request header

Task Breakdown

Once a story is ready for implementation, decompose it into technical tasks. Tasks are implementation steps, not user stories.

Horizontal vs Vertical Slicing

Vertical Slice (Recommended):

  • Delivers a complete user feature through all layers
  • Database → API → UI all implemented together
  • Produces working, testable feature
  • Example: "Implement payment history feature" (includes all layers)

Horizontal Slice (Avoid):

  • Implements one technical layer across multiple features
  • Creates dependencies between stories
  • Doesn't produce working feature until all slices complete
  • Example: "Create all database tables" (no user value)

Sample Task Breakdown

Story: As a customer, I want to view my payment history

Tasks:

Backend:
- [ ] Design database schema for payment_history table
- [ ] Create Flyway migration script
- [ ] Implement PaymentHistoryRepository (JPA)
- [ ] Implement PaymentHistoryService (business logic)
- [ ] Create PaymentHistoryController (REST endpoint)
- [ ] Add input validation and error handling
- [ ] Write unit tests (PaymentHistoryServiceTest)
- [ ] Write integration tests (PaymentHistoryIntegrationTest)
- [ ] Update OpenAPI specification

Frontend:
- [ ] Create PaymentHistory component (React)
- [ ] Implement API client for payment history endpoint
- [ ] Add loading, error, and empty states
- [ ] Implement pagination (Load More button)
- [ ] Add unit tests (PaymentHistory.test.tsx)
- [ ] Add integration tests (E2E with Cypress)

Documentation:
- [ ] Update API documentation
- [ ] Update user guide
- [ ] Add inline code comments for complex logic

DevOps:
- [ ] Configure monitoring/alerts for new endpoint
- [ ] Update deployment scripts if needed

Task Guidelines

Each task should:

Be estimatable: Ideally < 1 day of work Be independently testable: Can verify task completion Have clear acceptance criteria: Know when it's done Be assigned to one person: Clear ownership Be trackable: Can update status (To Do, In Progress, Done)


User Story Examples

Example 1: Payment Initiation

**Story**: Initiate Payment

**As a** bank customer
**I want to** initiate a payment to another account
**So that** I can transfer money quickly and securely

**Acceptance Criteria**:

1. Given I am logged in with sufficient balance
When I enter valid payment details (recipient, amount, description)
And I click "Submit Payment"
Then payment status shows "PROCESSING"
And I receive confirmation email within 1 minute
And my balance decreases by payment amount

2. Given I am logged in with insufficient balance
When I try to submit a payment
Then I see error "Insufficient funds"
And the payment button is disabled

3. Given I enter an invalid recipient account
When I try to submit
Then I see error "Invalid recipient account"
And payment is not initiated

4. Given payment processing takes > 10 seconds
When I'm waiting for confirmation
Then I see a loading indicator
And a message "Processing payment, please wait"

**Non-Functional Requirements**:
- Payment submission responds < 500ms (P95)
- Idempotency: Duplicate submissions within 5 minutes return original result
- Audit: All payment attempts logged with user ID, timestamp, amount
- Security: Amount and account details validated server-side

**Dependencies**:
- Account balance API available
- Recipient validation service available

**Estimate**: 5 story points

Example 2: Payment History

**Story**: View Payment History

**As a** bank customer
**I want to** view my recent payment transactions
**So that** I can track my spending and verify completed payments

**Acceptance Criteria**:

1. Given I am logged in
When I navigate to "Payment History"
Then I see my 10 most recent payments

2. And each payment displays:
- Date and time (format: DD/MM/YYYY HH:MM)
- Recipient name
- Amount (format: $X,XXX.XX)
- Status: Completed, Pending, Failed

3. Given I have more than 10 payments
When I scroll to the bottom
Then I see "Load More" button
When I click "Load More"
Then 10 more payments appear

4. Given I have no payment history
When I navigate to "Payment History"
Then I see "No payments found. Initiate a payment to see it here."

5. Given I filter by status "Failed"
When I apply filter
Then I see only failed payments

**Performance**:
- Page loads < 1 second
- "Load More" pagination < 500ms

**Security**:
- Users can only see their own payments
- No sensitive data in URLs

**Estimate**: 3 story points

Example 3: Spike Story

**Spike**: Investigate Payment Gateway Integration Options

**As a** development team
**We want to** evaluate payment gateway providers (Stripe, Square, Adyen)
**So that** we can select the best fit for our payment processing needs

**Acceptance Criteria**:

- Compare 3 providers on:
- Transaction fees
- API ease of integration
- PCI-DSS compliance support
- Geographic coverage
- Fraud detection capabilities
- Developer experience

- Document findings in Confluence
- Provide recommendation with justification
- Estimate effort for integration (story points)

**Timebox**: 8 hours
**Output**: Technical comparison document + recommendation
**Follow-up**: Create implementation stories for chosen provider

**Estimate**: 3 story points

Anti-Patterns to Avoid

Technical Task Disguised as Story

As a developer, I want to create a payment database table
so that we can store payment data.

Problem: Written from developer perspective, not user perspective. No user value.

Fix: Combine with user-facing story or frame as a technical task.

Too Vague

As a user, I want better payment performance.

Problem: Not estimable, not testable. What is "better"?

Fix: Be specific about what aspect of performance and how much improvement.

As a user, I want payment history to load in < 1 second
so that I can quickly review my transactions.

Implementation Details in Story

As a developer, I want to implement a Redis cache with 5-minute TTL
using Spring Cache annotations so that the API is faster.

Problem: Prescribes the "how", not the "what". Not negotiable.

Fix: Focus on user outcome, leave implementation to technical design.

As a user, I want payment history to load quickly (< 1 second)
so that I don't have to wait.

Too Large (Epic, Not Story)

As a user, I want a complete payment management system with history,
refunds, scheduling, and fraud detection.

Problem: Too large to complete in one sprint. Not small.

Fix: Break into multiple stories, each delivering value independently.

No Acceptance Criteria

As a user, I want to see payment history.

Problem: Not testable. No shared understanding of "done".

Fix: Add explicit, testable acceptance criteria.


Further Reading


Summary

Key Takeaways:

  1. INVEST Principle: Independent, Negotiable, Valuable, Estimable, Small, Testable
  2. User Perspective: Write from user's point of view, not developer's
  3. Value Focus: Every story must deliver clear value
  4. Acceptance Criteria: Use Given-When-Then format for testability
  5. Small Stories: Target 1-5 story points, complete within a sprint
  6. Vertical Slices: Implement full features through all layers, not horizontal layers
  7. Negotiable Details: Specify "what" and "why", not "how"
  8. Conversation Starter: Stories are placeholders for discussion, not complete specs
  9. Task Breakdown: Decompose stories into concrete technical tasks
  10. Avoid Anti-Patterns: No technical tasks disguised as stories, no implementation details