DEV Community

Cover image for Why 'Two Weeks' Always Means Six — and How to Estimate Honestly
Iurii Rogulia
Iurii Rogulia

Posted on • Originally published at iurii.rogulia.fi

Why 'Two Weeks' Always Means Six — and How to Estimate Honestly

A developer says "two weeks." Six weeks later, it ships.

This happens so reliably that whole project-management methodologies exist mostly to manage around it. And the standard explanations are wrong, or at least lazy. "Developers are bad at estimating" is the comfortable one — it puts the fault in a personality trait, which means nobody has to change how they ask for numbers. It also doesn't survive contact with the evidence: the same developers estimate their commute, their grocery run, and their weekend renovation with the same optimism, and so does everyone else. The bias isn't a coding skill defect. It's structural.

So here is the question worth answering, because the answer is actionable: how to estimate software projects honestly — knowing in advance why the number bends, and giving a figure your business can actually plan against. I've been giving estimates, and being held to them, for a long time. I've been wrong in every direction. What follows is why "two weeks" becomes six, and the specific things I now do to give numbers that don't.

The iceberg under "the happy path"

When a developer pictures a feature and says "two weeks," they are almost always estimating the happy path. The part they can see. User clicks the button, the data is valid, the third-party API responds, the record saves, the page renders. That mental movie runs in a few seconds and feels like the whole job.

It is maybe a third of the job.

Below the waterline sits everything the demo never shows. Input validation. The empty state, the loading state, the error state — three screens nobody mentioned but all of which have to exist. Authentication and authorization on the new route. The database migration, and the rollback for when it goes wrong. Error handling for the API call that times out. Idempotency for the request the user double-clicks. The deploy. Code review, and the round of changes it produces. QA finding the thing you didn't, and the back-and-forth to fix it. Then the second bug QA finds because the first fix broke something adjacent.

None of that is in the two-week movie, and none of it is optional. A feature that handles only the happy path isn't 90% done; it's a prototype that happens to demo well. The gap between "works when I click through it once" and "works in production for strangers doing things I didn't predict" is where the other four weeks live.

This is the single biggest source of estimate error, and it's not arithmetic. It's a perception problem: the part of the work that's easy to imagine is a small and unrepresentative fraction of the part that has to be built.

Effort is not duration

The second structural error is quieter and just as expensive. A developer estimates effort — "this is about three days of work" — and the number gets recorded, and reported upward, as duration: three calendar days, done Thursday.

Those are not the same quantity, and the conversion factor is brutal. Three "ideal days" of focused work do not fit into three working days. They fit into a working week and a half, because the working day is not eight hours of the estimated activity. It's standups and the sync that ran long. It's the context-switch tax every time someone asks "quick question." It's reviewing someone else's PR because the team blocks if you don't. It's the interview you're on the panel for. It's lunch and the commute that lives in the calendar even when remote.

Industry-wide, the gap between ideal engineering hours and elapsed calendar time tends to land somewhere around half — you get roughly four to five productive hours of the estimated work into a nominal eight-hour day, and less on a meeting-heavy one. So an honest effort estimate of "five days of work" is, before anything goes wrong, closer to two calendar weeks of wall-clock time. The estimate wasn't wrong. The translation was missing.

When I give a number now, I'm explicit about which currency it's in. "Five days of effort" and "ready in two weeks" are different sentences, and conflating them is how a correct estimate produces a broken commitment.

The things you can't estimate because you haven't met them yet

The third reason is the honest one, and it's the reason no technique fully closes the gap: you cannot estimate what you haven't discovered yet.

The payment provider's documentation says the webhook fires once. In production it fires three times, out of order, and one of them arrives before the API call that triggered it has returned. The legacy table has a column called status that's authoritative in three places and ignored in a fourth, and nobody alive knows why. The "simple" integration turns out to rate-limit at a number the docs don't mention, discovered only under real load. The third-party API lies — returns 200 OK with an error nested in the body.

These are unknown-unknowns, and they are not a sign that the estimator was careless. They are genuinely invisible at estimation time. You find them by building, because building is the act of discovery. Every non-trivial feature contains some number of these, and the number is itself unknown. This is exactly why I won't quote a rescue engagement before I've spent time in the codebase — most of the cost lives in things the repository hasn't shown me yet, and I wrote up that whole audit method in technical due diligence before a rewrite.

You cannot estimate these away. But you can estimate their existence — you know they're coming, even if you don't know their shape — and that's what a range is for.

Scope creep and the "while you're in there" tax

Then there's the part that isn't a misperception at all — it's the work genuinely growing under your feet.

"While you're in there, can you also…" is the most expensive sentence in software, because each instance is individually reasonable and collectively unbounded. The feature that was scoped as one form acquires a second field, then validation on that field, then a special case for one customer, then an admin view to manage the special case. Nobody decided to triple the work. It accreted, one defensible request at a time.

The estimate was honest for the feature that was described. It was never updated for the feature that was actually built, because the growth happened in conversation, not in a ticket. An estimate is a statement about a fixed scope; the moment the scope moves, the estimate is stale and almost nobody re-states it.

Optimism, anchoring, and the number that bends

The last reason is psychological, and the cheapest to fix once you see it.

A stakeholder asks, "this is pretty simple, right?" — and the number bends toward the answer they're hoping for. Not through dishonesty. Through anchoring: the word "simple" is now in the room, and "six weeks" feels like a confrontation, so "a couple of weeks" comes out instead. The developer wants to be helpful and competent, and the social path of least resistance is the smaller number.

Optimism bias does the rest. We imagine the version of the project where nothing goes wrong, because that's the version that's easy to imagine — the failures are, by definition, the things we haven't pictured. So we estimate the best case and report it as the expected case.

The fix is not "be more pessimistic." Pessimism is just optimism's equally uncalibrated twin. The fix is structural, and it's the rest of this article.

How to estimate honestly: ranges, not single numbers

The single most important change: stop giving single numbers. A single number is a lie of precision. It claims a confidence the situation doesn't contain.

Give a range, and attach meaning to its ends. Not "two to six weeks" mumbled as a hedge — that's a single number with anxiety. A real range says what each end means:

Two weeks if the payment integration behaves the way the docs claim. Five if it fights us the way these integrations usually do. My honest expectation is three.

That sentence is more useful to a business than any single number, because it tells the recipient what they're betting on and where the risk sits. If they need it in two weeks, the conversation is now about the payment integration specifically — the actual source of uncertainty — instead of about whether the developer is sandbagging.

If your organization speaks the language, frame it as confidence intervals: a P50 (half the time it's done by here) and a P90 (nine times in ten it's done by here). The distance between P50 and P90 is the most honest thing in the estimate — it's a direct measurement of how much you don't yet know. A wide gap isn't incompetence. It's an accurate report of genuine uncertainty, and a narrow gap on a vague feature is the actual red flag.

Decompose until each piece is a day — and time-box what you can't

You cannot estimate a big thing. You can only estimate small things and add them up. So decompose the work until every piece is roughly a day or less. Pieces that small are things you've done shapes of before; your gut is calibrated on them. "Build the reporting feature" is a guess. "Add the date-range picker, write the aggregation query, build the empty state, add the CSV export, handle the no-data case" is five things you can each actually picture.

The decomposition does double duty: the pieces you can't break down are exactly the risky ones. If you can't decompose "integrate the partner's API" into day-sized chunks, that's not an estimate waiting to happen — it's a research spike. So don't estimate it. Time-box it: "I'll spend one day finding out how this API actually behaves, then I'll estimate the integration." A timebox is a commitment to spend a fixed amount of learning, not a guess at the cost of building. Conflating the two — estimating the build of a thing you don't yet understand — is where the worst overruns come from.

Separate the estimate from the commitment

This is the distinction most teams collapse, and collapsing it is why estimates feel like traps to everyone involved.

The estimate is your honest, technical best guess at the effort. It belongs to engineering. It's a probabilistic statement about uncertain work.

The commitment is the date the business promises to a customer, a board, a launch. It belongs to the business, and it should include a buffer for the risk the estimate just quantified.

These are different objects owned by different people. When a developer's raw estimate gets promised verbatim to a customer as a hard date, the buffer that should have absorbed the unknown-unknowns was never added — and the first surprise blows the commitment. The honest move is to make the seam visible: "My estimate is three weeks. Given what we don't know about the integration, I wouldn't promise the customer anything tighter than five." The buffer is stated, owned, and defensible — not smuggled in by secretly tripling the number, which is the dishonest version everyone resorts to when the seam is hidden.

slug="fractional-cto"
text="If your estimates keep arriving as single comforting numbers and landing as overruns, that's a process gap I fix from the inside — separating engineering's estimate from the business's commitment, and putting calibration in place."
/>

Track estimate vs actual, and trust history over your gut

Your gut is uncalibrated until you measure it. The single highest-leverage practice in estimation is also the most neglected: write down what you estimated, then write down what it actually took, and look at the two columns.

After a dozen rows, patterns appear that no amount of careful thinking would have surfaced. You discover you're consistently 2.2x light on anything touching authentication. You discover CRUD features land almost exactly on estimate, but anything involving a third-party API runs triple. That's reference-class forecasting, and it beats fresh judgment every time: "the last three features that looked like this took four weeks each" is worth more than any amount of reasoning about why this one will be different. It usually won't be.

This is also the cheapest competitive edge a team can build. A developer who knows their own historical multiplier gives numbers that come true. One who estimates from scratch every time keeps relearning the same lesson and keeps surprising everyone, including themselves.

The cone of uncertainty: a stale estimate is a lie

There's a well-documented shape to estimation error called the cone of uncertainty. At the very beginning of a project — the idea stage, before anything is built — estimates are reliably off by a factor of around four in either direction. Not because everyone is bad at it; because there's genuinely four-x worth of unknowns still undiscovered. The cone narrows only as you build and learn. By the time you're halfway through, the same estimate might be off by 25%. By the end, it's off by nothing, because it's done.

The operational consequence is blunt: an estimate has a shelf life, and re-estimating at milestones isn't admitting failure — it's the job. The number you gave at the idea stage was your honest best guess given near-total ignorance. Three weeks in, you know things you didn't, and the responsible move is to update the number out loud. A team that holds you to an idea-stage estimate after a month of discovery is asking you to honour a guess made by someone who knew less than you do now.

The opposite failure is just as common: giving the idea-stage estimate, then never revisiting it, and letting it quietly become a broken promise. A stale estimate that nobody updated is, functionally, a lie — not because anyone intended to deceive, but because it stopped describing reality and nobody said so.

Name your assumptions, or the number means nothing

Every estimate rests on assumptions, and an estimate detached from its assumptions is worthless — worse than worthless, because it looks authoritative while being unconditioned.

"Three weeks" means nothing on its own. "Three weeks, assuming the staging environment matches production, the partner's API does what its docs claim, and nobody changes the requirements mid-flight" is a real estimate — because now both sides can see exactly which load-bearing beliefs the number stands on, and watch for the moment one of them fails. When the partner's API turns out to behave differently, you don't have an unexplained overrun; you have a named assumption that broke, and a conversation that starts from "remember assumption two" instead of "why are you late."

I write the assumptions down, next to the number, every time. It's the difference between an estimate someone can interrogate and a number they can only resent when it's wrong.

The honest-CTO framing: who you can plan around

Here is the part that's really about hiring, not arithmetic.

A good engineer — or a good fractional CTO, or a good contractor — gives you a number you can plan around together with its uncertainty. They will tell you the range, name the risk, separate the estimate from the commitment, and update you when the cone narrows. That is not them being evasive or covering themselves. That is them being useful. The uncertainty is real whether or not they tell you about it; the only choice is whether you find out now or in week five. I've written more about what that role actually involves in what a fractional CTO does, and the same posture shows up in the rewrite-or-stabilise decision — the honest answer is the conditional one.

And here is the filter, stated plainly: the client who wants a single small number no matter what the work is — who hears the range and pushes for "just give me one number, just tell me two weeks" — is asking to be lied to. They will get their single small number from someone, because someone will always say what a client wants to hear. That someone will then be late, and the client will be surprised, and the cycle repeats with the next contractor. If you, as a buyer, punish honest ranges and reward confident single numbers, you are training your suppliers to deceive you, and you will get exactly the estimates you've selected for.

The engineer who refuses to compress an honest range into a comforting lie is the one worth keeping. The discomfort of hearing "three to five weeks, here's why" is the price of a number that comes true. I'd rather lose the engagement at the estimate than lose your trust in week six — and a contractor who feels the same way is the one you want holding your timeline.

Takeaways

  • "Two weeks" is the happy path. The iceberg below — validation, error states, auth, migrations, review, QA — is most of the real work and none of the mental movie. Estimate the iceberg, not the demo.
  • Effort is not duration. Ideal engineering hours convert to calendar time at roughly half; say which currency your number is in.
  • You can't estimate unknown-unknowns away — but you can estimate that they exist, which is what a range is for.
  • Give ranges with meaning attached, not single numbers. The distance between P50 and P90 is an honest measure of what you don't yet know.
  • Decompose to day-sized pieces. What won't decompose is a research spike — time-box the learning, then estimate the build.
  • Separate the estimate from the commitment. Engineering owns the honest guess; the business owns the buffered promise. Make the buffer visible, don't hide it in a secret multiplier.
  • Track estimate vs actual and trust history over gut. Your own multiplier on auth, or on third-party APIs, beats fresh reasoning every time.
  • Re-estimate at milestones. The cone of uncertainty narrows as you learn; a stale estimate nobody updated is functionally a lie.
  • Name your assumptions next to the number. An estimate without its assumptions can't be interrogated, only resented.
  • The honest filter: a contractor who gives you a range with its risk is the one to keep. The client who demands a single small number regardless of the work is training their suppliers to lie.

Top comments (1)

Collapse
 
unitbuilds profile image
UnitBuilds

Imo. The scope defines the overshoot. I've had many under scoped specs for implementations and 3 days turn into 3 weeks, into 3 months... 3 days for the MVP, 3 weeks for stable product, 3 months for client refined.

whereas the 1 singular time I got an actual thorough scoped task, 4 pages documenting a single feature... It was done in 6 hours. Same size as the rest of the tasks, the only difference being it was an internally scoped (properly) feature, vs a client defined vague feature.

That is why you should scope it in 3 tiers. Initiation, Test, Deploy. Total cost of work split into 3 payments, defined by the state of the feature. Initiation is upfront. Test is the long one, where the client has to test it parallel with production to refine the scope (this is where you bill them per hour for requests), then production (when they switch their daily workflow over to the new system and the feature is signed off as complete). Scope your billing during test at 50% hourly rate, production 100%. That way you clearly define the scope of work and once they refine, you charge per hour, once it's making them money, you charge them clean rate due to any additions being the equivalent of a whole new dev job.