TL;DR: A software estimate goes wrong for predictable reasons: it is given as a single number, it is built from the parts you understand and ignores the parts you do not, and it leaves out the time that belongs to other people (app store review, carrier registration, a client's API access, a field test). The way I estimate now is to break the work into pieces small enough to picture, label each one as known, similar-to-something-I-have-done, or genuinely new, give a range instead of a number, run a short spike on anything new before committing, and list the external waits as their own line items. The range narrows as the unknowns get resolved, and I say so up front.
I have been asked "how long will this take?" more times than any other question in seven years of shipping software, first as a freelance Unity developer, then as a CTO for startups in the US and Italy, and now building AI voice agents and my own products. I have also been wrong about the answer, sometimes badly. This is the method I ended up with, mostly by learning which kinds of mistakes kept repeating.
Why software estimates go wrong
The usual explanation is that developers are optimistic. That is true but not very useful. The more specific problem is that estimates are built from the work you can see, and the expensive work is usually the work you cannot see yet.
When someone describes a feature, my brain fills in the familiar parts immediately. A login screen, a dashboard, a booking flow: I have built versions of all of these, so I can picture the work and size it. What I cannot picture is the part that is new for this specific project, and an estimate built only from what I can picture is quietly assuming the new part takes zero time.
There are three repeat offenders I now look for on purpose:
- Integration with something I do not control. A third-party API with thin documentation, a CRM with odd field rules, a hardware SDK. The happy path takes a day. Discovering how it behaves when something is missing takes the rest of the week.
- Time that belongs to other people. App Store review, Play Console checks, carrier registration for business texting, a client who needs a week to get you API credentials. None of this is engineering time, and all of it is calendar time.
- The last 20 percent. Edge cases, error states, the empty screen, the slow network, the old app version still installed on a phone somewhere. This is where demos become products, and it is consistently the part that gets estimated as "polish."
Step 1: Break the work down until you can picture each piece
An estimate for "build the app" is a guess. An estimate for "a screen that lists a player's past sessions, loads from the existing API, and handles the empty case" is something I can actually reason about.
I keep splitting until each piece is something I could describe in two or three sentences and imagine doing in a day or two. If a piece still feels foggy at that size, the fog is the signal: it means I do not understand it yet, and that piece needs a different treatment from the rest.
This is also where a good brief saves real money. A client who has written down what the product must do, who it is for, and what success looks like (the things I cover in how to write a software project brief) gets a tighter estimate, because there are fewer pieces I have to guess at.
Step 2: Label each piece known, similar, or new
Once the work is in pieces, I tag every one of them:
- Known. I have built this exact thing, on this stack, recently. A Retell agent with a booking function into a calendar, a Next.js admin page over an existing API, a Unity build pipeline. Estimates here are tight and usually right.
- Similar. I have built something close, but on a different platform or with a twist. A Unity feature I built for mobile AR that now has to run on a standalone headset, for example. I add a margin, because "similar" hides differences you only find while doing it. Moving work onto a headset taught me that performance alone can change the plan, which I wrote about in Unity performance on standalone VR headsets.
- New. I have not built this before, or the critical part depends on something I have not used. I do not put a confident number on these at all until I have done Step 4.
The labels matter more than the hours. A project that is 90 percent known and 10 percent new is a very different commitment from one that is half new, even if both add up to the same total on paper.
Step 3: Give a range, not a number
A single number communicates a certainty nobody has. I give a range for every piece and for the total: a realistic low if things go the way they usually do, and a realistic high if the unknowns bite.
For known pieces, the range is narrow. For new pieces, it is wide, and that width is honest information for the client. It tells them where the risk sits. When a client sees that most of the spread in an estimate comes from one integration, they can make a real decision: fund a quick investigation first, simplify that feature, or push it to a later phase. That conversation is the same one I describe in what to cut from an MVP, and a range is what makes it possible.
I also say how the range will change. Something like: "This is wide today because of the CRM integration. After the first week, once we have connected to it, I will give you a tighter number." Estimates are allowed to improve as you learn. What breaks trust is a number that silently drifts without anyone explaining why.
Step 4: Spike the unknowns before committing
For every piece tagged as new, I would rather spend a day or two finding out than a month being wrong. A spike is a short, throwaway piece of work whose only job is to answer a question: does this API actually return what the docs say? Can this headset run the scene at a usable frame rate? Does this voice platform let us transfer a call the way the client needs?
Spikes are cheap compared with what they prevent. When I was building and testing voice agents at Fortell AI, the fastest way to know whether an approach would hold up was to build a rough agent and call it, not to reason about it on paper. The same is true for most new technology. Ten minutes of real behaviour is worth more than an hour of reading.
The output of a spike is not code to keep. It is a better estimate, and sometimes a better plan. Occasionally the spike shows that the new piece is a much bigger job than anyone thought, and finding that out in week one is a gift compared with finding it out in week six.
Step 5: List the external waits as line items
This is the step most estimates skip, and it is where a lot of "late" projects actually lose their time. I write down every dependency on someone outside the team and give it its own entry with its own expected duration:
- App store review. Usually quick, sometimes not, and a rejection restarts the clock. At RAQTS I managed App Store and Play Console releases, and the calendar was never fully ours. I covered the common causes of delays in app store review rejections.
- Carrier and compliance registration. If a voice agent sends texts in the US, business messaging registration has to be done, and it runs on the carriers' timeline. More on that in A2P 10DLC registration for voice agent SMS.
- Access and credentials. API keys, CRM admin access, a test environment, a phone number. These often take longer to arrive than the work that depends on them.
- Real-world testing. Anything that lives in the physical world needs time there. AR work at ARCortex looked fine at a desk and behaved differently outdoors, which is why I treat field testing as its own phase, as I explain in testing AR apps in the field.
- Client review and feedback. Every review round needs time on both sides. I estimate it rather than pretend it is instant.
Putting these on the list does two things. It makes the estimate more accurate, and it makes it obvious which delays are not engineering delays, which avoids a lot of uncomfortable conversations later.
Step 6: Add the work everyone forgets
Before I hand over a number, I check the estimate against a list of things that are easy to leave out:
- Error states, empty states and loading states for every screen or flow.
- Handling old versions of a mobile app still in the wild after an update, a problem I covered in mobile app backward compatibility.
- Deployment, environments, and a build pipeline, which for Unity projects is real work, as in Unity CI/CD build pipelines.
- Testing, including regression tests for anything driven by a prompt or a model.
- Handover: documentation, credentials, and a walkthrough for whoever runs it after launch.
None of these are exciting, and all of them take time. Leaving them out does not make the project cheaper. It just moves the cost to a later, more awkward moment.
How I re-estimate during a project
An estimate is a starting point, not a promise carved in stone. On longer projects I revisit it at natural checkpoints: after the first spikes, after the first real integration, and before the final stretch. Each time, the unknowns are smaller, so the range gets narrower.
When the scope changes, I estimate the change on its own and say what it does to the timeline before starting it. That rule matters more than the pricing model you choose, and it applies whether the work is fixed price, hourly, or on a retainer. "Yes, we can add that, it adds roughly this much, do you want it in this phase or the next?" is a sentence that has saved more client relationships than any clever estimating formula.
The short version
Break the work down, label what is new, give ranges, spike the unknowns, list the external waits, and include the work everyone forgets. An estimate built that way is not guaranteed to be right. But when it is wrong, you will know why, and you will find out early enough to do something about it.
I scope and build software for founders across the US, UK and Europe, from AI voice agents to mobile apps, Unity and XR, and I have led engineering as a CTO for several startups. If you want an honest range for your project, with the unknowns named, more about me is here, or book a call.