Everyone is bad at estimating, including senior engineers and people who’ve been building software for twenty years. Optimism bias is built in. You picture the clean path through the problem and forget the three wrong turns, the undocumented API, and the scope that doubles in week two.
Being wrong is normal. The mistake is estimating from your head instead of from the problem.
Here’s how I work through an estimate before committing to one.
1. Have I done this before?
Before asking how long something will take, ask how much you know about it.
If you’ve built something meaningfully similar, you have real evidence. How long did the last one take? What surprised you? What turned out harder than it looked? Anchor to that, then adjust for differences in scope and context.
If you haven’t done anything close to it, that’s important information too. Your estimate is less reliable, and you should say so. “I’ve built something like this before and it took about three days” is a very different statement from “I’ve never done this, but I think three days.” Both are estimates, but only the first is honest about its uncertainty.
The closer your past experience is to the current problem, the tighter your estimate can be. The further away it is, the more buffer you need, because unknowns are genuinely expensive. That isn’t padding.
2. Is there a library for it?
Before you estimate the implementation, find out whether you’re building from scratch at all.
It sounds obvious, but a lot of time goes into estimating and building things that already exist as well-maintained packages. Five minutes of searching can take days off an estimate. It can also show you that the “simple” thing you were going to wire up yourself is a solved problem with a hundred edge cases already handled.
The reverse matters too. Sometimes a library almost does what you need, and bending it to your requirements costs more than building the thing directly. Know that before you commit to it. Libraries give you leverage and constraints at the same time.
Check the obvious places for your stack: npm, PyPI, pkg.go.dev. Search GitHub for similar projects even if they aren’t packaged. An unmaintained five-year-old repo can still tell you a lot about how hard a problem is.
3. What are the absolute unknowns?
List every part of the project you’ve never touched. I don’t mean a bit unfamiliar; I mean things you’ve never worked with at all, like a new API, an infrastructure pattern you’ve never set up, or an algorithm you’d have to learn first.
These are your high-variance items, and each one could swallow your schedule. You can’t estimate time on something you’ve never done with any confidence. All you can do is acknowledge the unknown and factor it in.
Then ask of each one whether it’s on the critical path. Some unknowns are nice-to-haves or edge cases, and those are fine. The dangerous ones are those where the core feature doesn’t work until you’ve solved them.
For critical-path unknowns, stop estimating and start researching. Even an hour of reading, watching, or skimming documentation gets you from “no idea” to a rough sense of the problem’s shape, and that’s enough for a rough estimate. Without it you’re guessing.
4. Skim GitHub
Before you write any code or commit to a number, search GitHub for the problem. You’re there to calibrate, not to copy.
Search for the core terms: the API you’re integrating, the pattern you’re implementing, the thing you’re building. Look at how other people approached it. How many files did a typical implementation touch? How many issues are open? Are they mostly cosmetic, or are people still hitting fundamental problems?
A repository with 200 open issues and lots of “this doesn’t work with X” threads is telling you something. So is a small, clean implementation with clear commits. Other people’s solutions give you ground truth that your own mental model can’t.
This is especially useful for the unknowns from step three. If you’ve never used a particular API, find three or four real implementations and read the code. How complex is the auth flow? How much boilerplate is there? Any surprising dependencies? That turns an abstract unknown into a concrete one, and you can estimate concrete unknowns.
5. Build a rough MVP in the first two hours
This is the most important step, and the one people skip most.
Before you give a final estimate for anything longer than a day or two, spend two hours building the roughest possible version that touches every major part of the problem: the data flow, the integration points, and the tricky bits from steps three and four. Think proof of concept, nowhere near a polished MVP.
It doesn’t need to be good, handle errors, or be structured properly. It only has to tell you whether your mental model of the problem is right.
There’s some ego involved here, and I’ll admit it. Can you build a rough version in two hours? Often you can, and those two hours reveal more about the real scope than any amount of planning. You find the dead ends, the integration that doesn’t behave the way the docs say, and the simple-looking thing that needs three other things in place first.
After two hours you’ll know one of three things:
- The path is clear and about as complex as you thought, so your estimate was probably right.
- The path is clear but more complex than you thought, so your estimate needs to go up.
- The path has a problem, such as a dead end, a missing capability, or an incompatibility, and the approach may need rethinking before you estimate at all.
The third outcome is the most valuable. Finding that in a two-hour spike beats finding it halfway through a week of work.
Someone has probably already done it
Underneath all of this is a funny fact: whatever you’re building, someone has very likely built it already. Maybe not the whole system exactly as you need it, but the pieces are out there (the algorithm, the integration pattern, the data structure, the approach), scattered across GitHub repos, blog posts, Stack Overflow threads, and half-finished side projects.
Very little in software is genuinely new. Systems where nobody has ever touched the underlying problem are rare. Most of what we build day to day recombines existing patterns for a context that happens to be ours.
I find that freeing. It means the research steps above almost always turn up something useful. Someone has hit the edge case you’re worried about, written up the gotchas of that API, or open-sourced a rough version of what you’re planning.
For estimates, the implication is direct. If you can’t find anything (no library, no repo, no blog post, no prior art at all), either you’re searching wrong or you’re in genuinely uncommon territory. The first is fixable. The second means your unknowns are real, and your estimate should reflect that.
Most of the time, though, you’ll find something, and it will tell you more about how long the work takes than anything you could reason out from a blank page.
Putting it together
Before you give a number:
- Check your past experience. What have you built that’s actually similar, and how long did it take?
- Check for libraries. What’s already solved, and what are you really building?
- Identify the absolute unknowns. What’s on the critical path that you’ve never done?
- Skim GitHub. What do real implementations look like, and what do they tell you about the problem?
- Build a quick spike: two hours, rough, touching every part.
After that you have evidence to base an estimate on. You’ve seen the problem, you know where the hard parts are, and you know which unknowns turned out to be nothing and which are real.
Estimates are still estimates, and they’ll still be wrong sometimes. But they’ll be wrong in a calibrated way instead of an optimistic way, and that’s a big difference.
When you really screwed it up
Sometimes the process doesn’t save you. The spike looked clean, the unknowns seemed manageable, and then the project took three times as long as you said. You’re late, people are waiting, and the voice in your head starts: maybe I’m not good at this. Maybe I shouldn’t be doing this. Maybe I should go work on a farm.
That voice isn’t useful, but it isn’t random either. It shows up in exactly this situation because a bad estimate feels like proof of incompetence. It’s really proof that the problem was harder than it looked, which happens to everyone, at every level, on every team.
Here’s what I’d do.
Stop and work out what happened, as diagnosis and not self-criticism. Where did the estimate break down? Did an unknown turn out much bigger than expected? Did the scope change partway through? Did an integration behave completely differently from its documentation? Find the specific cause. “I just misjudged it” won’t help you next time; “I didn’t account for the auth flow being stateful across services” will.
Tell people early, not at the deadline. The worst thing you can do when an estimate is slipping is wait, because every day you wait, other people lose options. If you’re three days into a five-day estimate and you know it’ll take ten, say so now, not on day nine. People can work with updated information. They can’t work with a surprise.
That’s the part that feels hardest when imposter syndrome is running hot, because delivering bad news feels like confirming you’re incompetent. It works the other way. The people who stay quiet until the deadline are the ones who damage trust, and the ones who raise problems early are the ones others rely on.
Keep the estimate separate from your ability. One bad estimate, or several, doesn’t tell you whether you’re good at your job. It tells you estimation is hard and this problem had surprises in it. Every engineer you admire has blown estimates. The ones who always seem to be right are either working on very predictable problems or padding so heavily that “always right” doesn’t mean much.
If you do the diagnosis, a badly missed estimate teaches you more than ten correct ones, because you learn something real about your blind spots.
Then update the estimate and keep going. Once you’ve diagnosed it, told people, and recalibrated, all that’s left is to finish. The farm will still be there if you change your mind. For now, close the tabs, reset the three-item card, and build the next piece.