Case Study 1: How a Real Team Builds Software — A Sprint from Start to Finish
The Scenario
Meet the team at FreshCart, a startup building a grocery delivery app. They're a small team: Amir (Product Owner), Lucia (Scrum Master and developer), Dev (backend developer), and Keiko (frontend developer). They've been running two-week sprints for three months, and the app has basic functionality — users can browse products, add them to a cart, and place orders.
This case study follows one sprint from planning to retrospective. It's based on composite real-world practices, condensed for clarity, but everything described here happens in professional teams every day.
Monday Morning: Sprint Planning
The team gathers (remotely — the four of them are spread across three time zones) for sprint planning. Amir has prepared the backlog. The top items, ranked by priority:
| # | User Story | Size | Priority |
|---|---|---|---|
| 1 | As a customer, I want to see estimated delivery times so that I can plan my day. | M | Must have |
| 2 | As a customer, I want to apply a promo code at checkout so that I can get discounts. | L | Must have |
| 3 | As a driver, I want to see the optimal delivery route so that I can save time. | XL | Should have |
| 4 | As a customer, I want to reorder my last order with one click so that I save time on regular purchases. | S | Should have |
| 5 | As an admin, I want to view order statistics by day and week so that I can track business performance. | M | Nice to have |
Amir (Product Owner): "The delivery time estimate is our top priority. Customers keep asking when their groceries will arrive, and we're losing them to competitors who show estimates. The promo code is second — marketing has a launch campaign planned for next month and they need promo codes working."
Lucia (Scrum Master): "Based on our velocity from the last three sprints, we can handle about 2-3 medium stories per sprint. The route optimization is XL — that's a whole sprint by itself. I'd suggest we take stories 1, 4, and maybe 2 if we have capacity."
Dev: "The promo code story is larger than it looks. We need to handle percentage discounts, flat discounts, minimum order amounts, expiration dates, and single-use codes. Can we split it?"
This is a key Agile skill: recognizing when a story is too big and splitting it into smaller, deliverable pieces. The team splits the promo code story:
- 2a: As a customer, I want to enter a promo code at checkout so I can get a percentage discount. (S)
- 2b: As a customer, I want to see an error message if my promo code is invalid or expired. (S)
- 2c: As an admin, I want to create promo codes with expiration dates and usage limits. (M)
They commit to stories 1, 2a, 2b, and 4 for this sprint. Story 2c goes back to the backlog for next sprint.
Sprint Goal: "Customers can see delivery estimates and apply basic promo codes."
Tuesday Through Thursday: Building
Daily Standups
Every morning at 9:15 AM (chosen because it works across all time zones), the team holds a 15-minute standup. Each person answers three questions:
- What did I do yesterday?
- What will I do today?
- Is anything blocking me?
Wednesday standup:
Keiko: "Yesterday I built the delivery time display component and connected it to the mock API. Today I'll work on the promo code input field. No blockers."
Dev: "Yesterday I researched delivery time APIs. Our mapping service has a distance matrix endpoint, but it costs $0.005 per request. At our scale, that's $150/month. Today I'll build the backend endpoint with caching so we don't recalculate for the same area repeatedly. No blockers yet, but I might need Amir's input on the pricing."
Lucia: "Yesterday I set up the database schema for promo codes and wrote the validation logic. Today I'll write tests. One blocker — I need Amir to clarify: can a promo code be used more than once by the same customer?"
Amir: "For this sprint, let's keep it simple — each code can be used once per customer. I'll add multi-use as a separate story for later."
Notice what happened: Lucia's question could have blocked her for hours or days if she'd waited to bring it up. The standup surfaced the issue, and Amir answered it in 30 seconds. This is why daily standups exist — not to micromanage, but to catch blockers early.
The Unexpected Problem
Thursday afternoon, Dev discovers that the mapping service API has a rate limit of 100 requests per minute. During peak hours, FreshCart might exceed that. He has three options:
- Ignore it and hope for the best. (Technical debt — deliberate, reckless.)
- Add aggressive caching so most requests are served from cache. (Reasonable compromise.)
- Build a queue system that throttles API requests. (Robust but would take the rest of the sprint.)
Dev brings this to the team at Friday's standup. Lucia suggests option 2 with a backlog item for option 3 if caching proves insufficient. Amir agrees — "Ship what we can now, plan for scale next sprint."
This is deliberate, prudent technical debt. They know the caching isn't a permanent solution, but it's good enough for current traffic, and they've documented the limitation.
Friday (End of Week 1): Mid-Sprint Check-In
The team does an informal check-in:
- Story 1 (delivery estimates): Backend 80% done (caching in progress), frontend 90% done (connected to mock, waiting for real API)
- Story 2a (promo code entry): Backend validation done, frontend input field done, integration not started
- Story 2b (promo code errors): Backend error handling done (bundled with 2a), frontend error display not started
- Story 4 (reorder): Not started yet
Lucia raises a flag: "Story 4 hasn't been touched. We committed to it, but we've spent more time on delivery estimates than planned. Should we drop it from this sprint?"
The team discusses. Keiko estimates she can finish the promo code frontend and the reorder feature in the second week. They decide to keep it in the sprint but flag it as at risk.
Week 2: Integration, Testing, and the Code Review
The Code Review
Dev opens a pull request for the delivery time estimation backend. Keiko reviews it:
Keiko's review comments:
delivery_service.py, line 34: This function is 85 lines long and handles API calls, caching, time calculation, and error handling. Could we split it into smaller functions? Maybefetch_distance(),calculate_estimate(), andcache_result()?
delivery_service.py, line 52: The cache expires after 1 hour (hardcoded). Can this be a configuration variable? Different environments might need different cache durations.
delivery_service.py, line 71: If the API returns an error, you're catchingException, which is nearly as broad as a bareexcept— it will also swallow a typo like aNameErrorin your own code. Can we catchrequests.RequestExceptionspecifically? (Remember the Chapter 11 rule: catch specific exception types.)
test_delivery.py: Nice test coverage for the happy path. But there's no test for what happens when the cache is full, or when the API returns a 429 (rate limited). Could you add those edge cases?General: The docstrings are excellent — thanks for including examples. The
calculate_delivery_window()function is particularly clear.
Dev responds to each comment, makes the changes, and pushes a new commit. Keiko approves the PR. Lucia merges it.
Total time for the review: 45 minutes. Bugs caught: 2 (the overly broad except, which would have hidden real errors, and the missing handling for a 429 rate-limit response). Knowledge shared: Keiko now understands the delivery estimation system well enough to debug it if Dev is unavailable.
Integration Testing
With all stories integrated, the team spends Thursday running through the complete user flow:
- Open app -> browse products -> add to cart -> see delivery estimate -> apply promo code -> place order
They discover a bug: the checkout screen applies the percentage discount to the subtotal plus the delivery fee, while the payment backend (correctly) applies it to the subtotal only. So customers see a lower price than they're actually charged. The acceptance criteria said "discount applies to product subtotal, not delivery fee," and the backend's tests checked that rule, but nobody tested what the checkout screen actually displayed.
Keiko, who owns the checkout screen, fixes the bug in 20 minutes. Lucia adds a regression test (a test that pins down a bug you've already fixed) so it can't quietly come back.
End of Sprint: Review and Retrospective
Sprint Review (Demo)
The team demonstrates working features to Amir and two stakeholders from the marketing team:
- Delivery estimates: Working. Stakeholders are happy but ask if estimates can be shown as a range ("25-35 minutes") instead of a single number. Amir adds this as a new backlog item.
- Promo codes: Working for percentage discounts. Marketing asks about flat-amount discounts — Amir explains that's planned for Story 2c next sprint.
- Reorder: Working but only for the most recent order. Keiko notes she could extend it to show the last 5 orders in a future sprint.
All four committed stories completed: Story 4 (reorder) squeaked in on Thursday. Sprint goal met.
Sprint Retrospective
The team discusses three questions:
What went well? - Code review caught two bugs before they reached users - Splitting the promo code story was the right call — 2c would have been too much - Daily standups surfaced the rate limit issue quickly
What didn't go well? - Story 4 almost got dropped because of underestimation on Story 1 - The integration bug (promo code applied to wrong total) should have been caught earlier
What will we change? - Add integration testing to the Definition of Done (the team's checklist for calling a story finished), so every story is tested end to end as it's completed, not just at the end of the sprint - When estimating, add a buffer for "investigation time" on stories that involve third-party APIs
Discussion Questions
-
The team chose option 2 (caching) for the rate limit problem instead of option 3 (full queue system). Under what circumstances should they revisit this decision? What signals would tell them the caching isn't sufficient?
-
Keiko's code review caught two bugs that Dev missed, in code from Dev's own specialty (the backend). Suppose Dev is also the more experienced developer. Would that surprise you? Why might a reviewer with less experience in an area catch things the author missed?
-
The integration bug (promo code applied to wrong total) was caught during manual testing. How could this have been caught earlier? Write a
pytesttest case (pseudocode is fine) that would catch this specific bug. -
Amir (Product Owner) decided that promo codes should be single-use per customer for this sprint, deferring multi-use to later. Is this an MVP decision or a compromise? What's the difference?
-
The retrospective identified that integration testing should happen earlier. But earlier integration testing means less time for feature development. How would you balance these competing demands? Is there a way to make integration testing less time-consuming?
Key Takeaway
A sprint is not just "two weeks of coding." It's a structured cycle of planning, building, reviewing, testing, demonstrating, and reflecting. Each ceremony exists to solve a specific problem: planning prevents chaos, standups catch blockers early, reviews catch bugs, demos gather feedback, and retrospectives improve the process itself. The discipline of the process is what enables the team to move fast without breaking things.