We Finally Shipped the Thing We Promised Seven Years Ago. Here's Why It Took So Long.
In this article
Bottom line: Software promised years ahead is almost always late for three reasons: the original scope was a guess, the foundation had to be rebuilt before the feature could exist, and the team defended the promise instead of testing it.
Duke Nukem Forever (announced 1997, shipped 2011) and Python 3's migration, which ran from 2008 to Python 2's end of life in January 2020, followed the same pattern.
We just shipped the feature we announced in 2019, and our delay maps to all three causes.
I promised this in 2019. I put a date on it, told customers, and told myself it was a quarter or two of work. It took seven years, and I'm not proud of that number.
I'm not going to write the "lessons learned" post where every delay turns out to be a noble pursuit of quality. Some of our delay was real engineering.
Some of it was me being wrong in public and refusing to say so. This article separates the two, because I couldn't do that honestly until I'd shipped.
The Test: Audit Our Own Seven Years
Here's how I kept myself honest. I went back through our issue tracker, planning docs, and old roadmap decks and classified every month of the seven years into one of three buckets:
- Building: work that moved the shipped product forward.
- Rebuilding: work on foundations the feature depended on, which we hadn't known we'd need.
- Defending: time spent protecting the original promise, the original design, or the original date.
The rules were simple. If I couldn't point to a commit, a document, or a decision log entry, the month didn't get a bucket. If a month was ambiguous, it went to the less flattering category.
I'm the founder, so nobody was going to argue with me.
I expected "Defending" to be tiny. It wasn't, and I'll come back to it.
Round 1: The Estimate Was Never an Estimate
Our 2019 date was a wish with a spreadsheet attached. Nobody lied. We just never broke the work down far enough to find out what we didn't know.
This is the oldest story in software.
Fred Brooks wrote about it in The Mythical Man-Month in 1975, and Hofstadter's Law is the joke version: it always takes longer than you expect, even when you account for Hofstadter's Law.
Knowing this didn't stop me.
The tell was in how we talked about the project. We said "just" constantly: just add a field, just expose the API, just migrate the data. Every "just" was a hidden dependency we hadn't looked at yet.
Duke Nukem Forever is the famous case. It was announced in 1997 and didn't ship until 2011, partly because the team kept switching engines to chase whatever looked newest. We didn't switch engines.
But we did the equivalent. Twice we re-scoped the design because a new group of customers showed up and wanted something slightly different, and each time we told ourselves the date was still safe.
Round 2: The Part Nobody Warns You About
The foundation wasn't there
The feature we promised sat on top of an underlying layer that, in 2019, I believed was close enough. It wasn't.
This is the pattern that kills timelines. Python 3 shipped in December 2008, and the ecosystem needed more than a decade to move.
Python 2 only reached end of life on January 1, 2020, after the original sunset date had slipped.
The language change itself wasn't the hard part. The hard part was everything underneath it that had to move first.
Rust is the happier version. It began as a personal project of Graydon Hoare's, got Mozilla's backing around 2009, and reached 1.0 in May 2015.
That's years of work on foundations before anyone could call it stable. Nobody calls that a failure now, but in year four it would have looked like one.
The rewrite temptation
Somewhere in year three we had the meeting. You know the one: "it'd be faster to just start over." I wanted that rewrite badly.
Joel Spolsky wrote in 2000 that rewriting from scratch is "the single worst strategic mistake" a software company can make, using Netscape as the cautionary tale. He's mostly right, and I've lived it.
But rewrites have a time and a place, and teams are still doing them.
WhatsApp's media handler move to Rust is a recent example of a rewrite aimed at one contained component rather than the whole product.
We landed in the middle: we replaced the one subsystem that was blocking us and kept everything else. That compromise cost us months, and I think it was still the right call.
The part that's on me
Here's the bucket I was embarrassed by. For a stretch, we weren't building anything.
We were defending the original design because it had been announced, because customers had seen screenshots, and because changing it meant admitting the 2019 plan was wrong.
A public promise turns into a cage. Every time the real requirements drifted from the announcement, we had two options: fix the product, or protect the story.
More often than I'd like, we protected the story.
The Results: What Seven Years Actually Bought
I'll give you the unflattering summary first.
| Cause | Avoidable? | What would have helped |
|---|---|---|
| Bad initial estimate | Partly | Smaller scope, earlier breakdown of the work |
| Missing foundations | Mostly not | Nothing; we couldn't have known until we tried |
| Defending the promise | Yes, entirely | Saying the announced design was wrong, sooner |
The foundation work was the legitimate part. It's the reason the shipped version is better than the one we'd have shipped in 2020.
I believe that, because the product now does things the old foundation could never have supported.
The "defending" bucket was pure waste. If I'd said in 2021 "the design we announced is wrong, here's the new one," we'd have shipped sooner and lost less credibility than we did by staying silent.
What This Means If You're Making Promises
I'm not telling you to stop announcing things. Public commitments create real pressure, and some of the best work I've seen came out of it. But three habits would have saved me years:
- Promise outcomes, not designs. "Customers will be able to do X" survives a redesign. "We're shipping Y with these screenshots" doesn't.
- Date the uncertainty. Say "we'll know the real date by March" instead of picking one now. Make the first milestone about discovering the unknowns.
- Schedule your own re-announcement. Every six months, ask: if we were announcing this today, would we announce the same thing? If not, say so publicly. It's embarrassing for a day and cheaper than years of quiet drift.
If you're a small team or a solo builder, the failure mode is slightly different. You don't have a committee defending the plan, just your own ego. The fix is the same.
Write down what you promised, then ask whether you're still building that or just still defending it.
The Twist
The thing that surprised me most wasn't the timeline. It was how little the customers cared once it shipped.
Most people who'd been waiting didn't mention the delay at all. They asked whether it did the thing they needed. The seven years lived in my head and my team's.
People who'd been waiting wanted a working product, not an apology tour.
That doesn't excuse the delay. It does tell me how much of the pain was self-inflicted. I treated the promise as a debt to be paid with perfection, when what people wanted was something that worked.
So here's what I'm curious about: what's the oldest promise you've made, to customers, to your team, or to yourself, that you're still defending instead of building?
And if you audited it the way I audited mine, which bucket would the last year fall into?