We Finished the Feature in a Week. It Shipped Three Months Later.

A client's team built the core feature in five days. Then came the edge cases, the migration script, the security review, the load test that failed, and the staging bug that took longer to fix than the feature itself.


Last October, I watched a team of four engineers build a payments reconciliation feature in five days. The code was clean. The tests passed. The demo went well. The product manager sent a Slack message to the company channel: "Payments reconciliation is done! Rolling out next week."

It shipped in January.

The gap nobody plans for

I see this pattern constantly. A team estimates the work, builds the thing, and then hits a wall of tasks that nobody accounted for. Not because they're bad at planning — because the work between "code complete" and "production ready" is genuinely hard to see until you're standing in front of it.

Here's what happened with that payments feature, week by week.

Week 1: Core feature built. Unit tests passing. Happy path works perfectly. Team feels great.

Week 2: QA finds that the reconciliation logic breaks when a payment is partially refunded, then refunded again, within the same billing cycle. Nobody had considered double partial refunds. The fix touches three services.

Week 3: The data migration script for existing payment records works fine on the dev database (2,000 rows). On a copy of production (11 million rows), it takes 9 hours and locks the payments table. Back to the drawing board.

Weeks 4–5: Security review. The feature exposes a new API endpoint that accepts payment IDs. The security team flags that payment IDs are sequential integers, which means an attacker could enumerate them. The team switches to UUIDs, and this cascades into changes across the frontend, two backend services, and the mobile app.

Weeks 6–7: The migration script is rewritten to run in batches of 5,000. It works, but someone notices that the reconciliation totals don't match for accounts created before 2024 — a timezone change in the billing system that nobody documented.

Week 8: Load testing. The reconciliation endpoint performs fine under normal traffic. Under peak load — the kind that happens on the first of the month when everyone's billing cycle resets — response times hit 12 seconds. The query plan needs rethinking.

Weeks 9–10: Performance fix deployed to staging. Staging uses a different payment provider sandbox than production, and the sandbox has a rate limit the team didn't know about. The feature only passed load testing because staging was never exercising the real bottleneck.

Weeks 11–12: Integration testing with the actual payment provider in a pre-production environment. A webhook handler has a race condition that only manifests under concurrent updates. Two engineers spend a week reproducing it and another week fixing it.

Week 13: Ship it. For real this time.

Why this keeps happening

I've watched enough projects to notice a pattern. Teams are actually pretty good at estimating the core work. If you ask a senior engineer "how long to build the reconciliation logic," they'll give you a reasonable answer. The part that's consistently underestimated — or just invisible — is everything else.

Data migrations. Security reviews. Performance under realistic load. The weird edge cases that only exist in production data. Integration testing with third-party services that behave differently than their documentation promises. The staging environment that gives you false confidence because it's not actually representative of production.

These aren't unknown unknowns. They're known categories of work that get zero time allocated because they're not the feature. Nobody writes a Jira ticket that says "discover and fix the timezone inconsistency in legacy billing data." That work doesn't exist until you're already in the middle of it.

I started asking clients a question during project kickoffs: "What percentage of your estimate is for the feature itself, and what percentage is for everything needed to ship the feature safely?" Most teams look at me blankly. The generous ones say 90/10. The honest answer, in my experience, is closer to 40/60.

What actually helps

I'm not going to pretend I've solved estimation. Nobody has. But three things have consistently reduced the gap at my clients.

Ship-readiness checklists. Not process for the sake of process — a literal list of things that must be true before a feature goes to production. Data migration tested at production scale. Security review complete. Load test run with realistic traffic patterns. Monitoring and alerting configured. Every team I've worked with that maintains one of these has better estimates, because the checklist makes the invisible work visible during planning, not after the code is written.

Tip

If your team doesn't have a ship-readiness checklist, start with five items. The right five will become obvious after your next production incident.

Migration dry runs on day one. If the feature touches existing data, run the migration script on a production-sized dataset before you write a single line of feature code. The migration is often the longest pole in the tent, and finding that out in week 6 instead of week 1 is how three-month delays are born.

Budget time for the third-party surprise. Any feature that integrates with an external service will encounter at least one behavior that doesn't match the documentation. This isn't cynicism, it's base rates. I've seen it at every client, with every payment provider, every identity platform, every shipping API. The only variable is which undocumented behavior bites you. Build a week of slack into any estimate that involves an external integration. You'll use it.

The uncomfortable truth

The payments reconciliation feature I described was well-built by a competent team. They didn't fail at engineering — they failed at scoping. The feature was a week of work. Shipping the feature safely was three months of work. Those are different projects, and they deserved different estimates.

The hardest part of this conversation is that "everything needed to ship" doesn't make for a satisfying sprint demo. Nobody gets excited about a batch migration script that handles timezone edge cases. The product manager doesn't send a company-wide Slack message when the load test finally passes. But that's the work. Most of it, actually.

I still see the "Payments reconciliation is done!" Slack message sometimes when I dig through that client's history. It sits there, dated mid-October, twelve weeks before the feature actually reached a customer. A small monument to the distance between writing the code and shipping the thing.