From a brief to user stories
A brief describes a system. User stories describe what somebody wants to do, and they are the unit you actually build in — because "the shop has a cart" is not something you can finish, and "a customer can add an item to their cart" is.
The shape
As a customer
I want to add an item to my cart
So that I can buy several things in one order
The third line is the one people skip and the one that matters. "So that" forces a reason, and a story with no convincing reason is a feature nobody asked for.
It also tells you what to do when the requirement is awkward. If the reason is "so I can buy several things in one order", then a cart that empties on refresh fails the story even though it technically has a cart.
Keep them in the user's language. Not "as a customer I want a POST /cart/items
endpoint" — that is a task, not a story.
Acceptance criteria
The story says what and why. The criteria say when it is done:
As a customer
I want to add an item to my cart
So that I can buy several things in one order
Given I am viewing a product that is in stock
When I choose a variant and tap Add to cart
Then the item appears in my cart with quantity 1
And the cart count in the header increases
And I stay on the product page
Given the item is already in my cart
When I add it again
Then the quantity increases rather than a second line appearing
Given the variant is out of stock
When I view the product
Then the Add to cart button is replaced with "Out of stock"
Given I am not signed in
When I add an item
Then it is still added, and it survives signing in later
Each becomes a test in module 15. Written this way, the tests almost write themselves — which is the practical reason to bother.
Write the awkward cases as criteria, not as an afterthought. "Already in the cart" and "not signed in" are where the real decisions are, and putting them here forces you to decide before you are mid-implementation.
The customer stories
Grouped by what they are trying to do.
Finding things
As a customer, I want to browse products by category,
so that I can find things without knowing exactly what I want.
As a customer, I want to search by name or brand,
so that I can find a specific thing quickly.
As a customer, I want to filter by price and availability,
so that I do not waste time on things I cannot buy.
As a customer, I want to see the price per unit,
so that I can compare a 1kg pack against a 5kg one.
Buying
As a customer, I want my cart to survive closing the browser,
so that I can decide over an afternoon.
As a customer, I want to change quantities in the cart,
so that I do not have to start again.
As a customer, I want to see my total including delivery before paying,
so that there is no surprise at the end.
As a customer, I want to choose a delivery slot,
so that I am at home when it arrives.
As a customer, I want to pay online or on delivery,
so that I can use whichever I trust.
Afterwards
As a customer, I want to see my order's status,
so that I know whether to expect it today.
As a customer, I want to cancel an order before it is packed,
so that a mistake does not cost me.
As a customer, I want to reorder a previous order,
so that my weekly shop takes one tap.
The owner stories
As the owner, I want to see new orders on my phone,
so that I can start packing without going to a computer.
As the owner, I want to mark an order packed in one tap,
so that it does not interrupt serving customers.
As the owner, I want to update stock quickly,
so that the shop does not sell what I do not have.
As the owner, I want to change a price,
so that I can follow my supplier's prices.
As the owner, I want to hide a product without deleting it,
so that old orders still make sense.
As the owner, I want to see what sold this week,
so that I know what to reorder.
Notice how many of the owner's stories end in "quickly" or "in one tap". That is the constraint from the brief showing up in the requirements, which is where it should show up — not as a note in a design document nobody reads.
Prioritising
Not all of these ship first. The usual split:
Must have — the shop cannot open without it. Browse, product detail, cart, checkout, place order, owner sees orders and marks them packed.
Should have — noticeably worse without it. Search, filters, order status for the customer, stock management, online payment.
Could have — genuinely nice. Reorder, delivery slots, sales reporting, price history.
Will not have this time — the exclusions from the brief.
Build the must-haves end to end before starting the should-haves. A shop where you can browse, add to cart and place an order is a working shop. A shop with beautiful search and no checkout is nothing.
This is the same argument as the Python capstone's thin slices, at a larger scale.
Stories that are too big
As a customer, I want to check out.
That is not a story, it is a module. You cannot estimate it, finish it in a sitting, or tell whether it is done.
Split it:
As a customer, I want to enter a delivery address.
As a customer, I want to choose a delivery slot.
As a customer, I want to see my total including delivery.
As a customer, I want to pay by card.
As a customer, I want to pay cash on delivery.
As a customer, I want to see a confirmation with my order number.
Six stories, each finishable, each independently testable.
A useful test: can you build it in a day and demonstrate it? If not, split it. The split usually reveals a decision you had not noticed — here, that cash on delivery and card payment are genuinely different flows.
Where the hard parts live
The four hard parts from the brief do not appear as stories, because they are not things a user wants. They appear as acceptance criteria on the stories they affect:
As a customer, I want to place an order...
Given an item in my cart went out of stock while I was shopping
When I try to place the order
Then I am told which item, and offered to remove it and continue
And no order is created
And nothing is charged
Given the price of an item changed after I added it
When I reach the checkout
Then I see the current price
And I am told the price changed before I pay
That second one is a decision, written down. We show the current price and tell the customer. The alternatives — honouring the old price, or silently charging the new one — are both defensible, and silently charging the new one is the one that generates complaints.
Writing it as a criterion means it gets built and tested rather than being decided by whichever line of code runs first.
Keeping them somewhere
GitHub Issues is enough, one per story, labelled by priority. A project board gives you columns.
What matters is not the tool. It is that the story is written down before it is built, and that "done" means the acceptance criteria pass rather than "the code is written".
Check your work
Why "so that" matters: it forces a reason, and a story without a convincing one is a feature nobody asked for. It also tells you what to do when a detail is awkward.
What acceptance criteria are for: defining done, and giving you your tests almost directly.
Where the awkward cases belong: in the criteria, so they are decided before implementation rather than during it.
Why must-haves go end to end first: a shop you can order from is a working shop; beautiful search with no checkout is nothing.
The test for a story being too big: you cannot build and demonstrate it in a day.
Why the hard parts are criteria rather than stories: they are not things a user wants, they are how the system must behave on stories the user does want.
Why the price-change criterion matters: it turns a decision into something built and tested, rather than something settled by accident.
Practice
- Write the six checkout stories in full, with acceptance criteria for each.
- Take "As a customer, I want to search for products" and write at least five criteria, including the no-results case and an empty query.
- Write the owner's "mark an order packed" story with criteria, including what happens if two staff tap it at once.
- Sort every story from this lesson into must, should, could and will-not. Defend two of your must-haves.
- Write the out-of-stock-at-checkout criterion yourself before re-reading the version above. Compare.
- Decide the price-change question from the last lesson and write it as a criterion.
- Take one of your stories and deliberately make it too big. Then split it and note what decision the split revealed.
- Put your must-have stories into GitHub Issues on your project repository.
Next: turning the stories into a data model.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship