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 — three of them are in different 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 catching a bareException. Can we catchrequests.RequestExceptionspecifically? (Remember the Chapter 11 rule about bare excepts.)
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 bare except and the missing rate-limit handling). 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 promo code discount is applied before the delivery fee is calculated, so customers see a lower price than they actually pay. The acceptance criteria said "discount applies to product subtotal, not delivery fee," but nobody tested that specific scenario.
Dev fixes the bug in 20 minutes. Lucia adds a regression test so it can't happen again.
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.
Three of four stories completed. Story 4 (reorder) was finished 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 for every story, 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. Dev is a more experienced developer. Does this surprise you? Why might a less experienced reviewer 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.