How to estimate a legacy modernization before you talk to a vendor
October 12, 2026
Most founders and engineering leads walk into their first vendor call with no number in their head. That is a weak position. If you cannot say roughly how big the job is, you cannot tell whether a quote is sensible, and you will anchor on whatever figure arrives first.
You do not need a precise estimate. You need a defensible range and a list of the things that would push you to either end of it. This post lays out a method you can run in an afternoon, with a hypothetical example so you can see the arithmetic. We deliberately do not put prices on it. Rates vary by market and by who is doing the work, and a number printed here would be wrong for most readers.
Step 1: Describe the system in units, not feelings
"Big old PHP monolith" is a feeling. Estimates need units you can count. Collect these, even roughly:
- Number of user-facing screens or endpoints that are actually used (check logs, not memory)
- Number of integrations: payment, email, CRMs, partner feeds, internal services
- Number of database tables, and how many have no documented meaning
- Count of background jobs, cron tasks and scheduled scripts
- Languages and framework versions in play, and how far behind each is
- Test coverage, honestly: do tests exist, do they run, do they pass
A few commands get you most of it:
# Rough size by language
npx cloc . --exclude-dir=node_modules,vendor,.git
# Which files change most (risk hotspots)
git log --since="12 months ago" --name-only --pretty=format: | sort | uniq -c | sort -rn | head -20
# Endpoints actually hit, from an access log
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -50
The last one is underrated. Systems accumulate dead routes, and you should not pay to migrate them.
Step 2: Score the five cost drivers
Across upgrade and modernization work, cost tends to be moved by the same few things. Score each one low, medium or high for your system.
Version distance. One major version behind is a different job from five. Frameworks document upgrade paths one hop at a time, and each hop has its own breaking changes. Distance multiplies, it does not add.
Test safety net. With good tests, an upgrade is mostly mechanical. With none, someone has to first build the net that lets you change things safely. That work is real and it comes first.
Hidden coupling. Shared databases, code that reads another service's tables, "temporary" scripts that production depends on. You find these by looking, and they are the usual source of surprises.
Integration surface. Every third-party API or partner feed is something to re-verify after the change. Ten integrations is not ten times one, but it is much more than one.
Knowledge loss. If nobody on your team understands a module, the first phase is archaeology. Budget for it explicitly.
Step 3: Turn scores into a range
Pick a base unit of effort for the cleanest version of your project, the one where everything scores low. Then apply a spread. A common and honest way to do it is a three-point estimate for each workstream: an optimistic case, a likely case and a pessimistic case. The pessimistic case is not a disaster, it is what happens if one or two of your "medium" scores turn out to be "high."
Two rules keep this from becoming fiction:
- The pessimistic number should be meaningfully larger than the likely one. If your range is tight, you are probably ignoring something.
- Write down what would move you from likely to pessimistic. Those are your questions for the vendor.
A hypothetical worked example
Everything below is invented for illustration. It is not a real client or a real project.
Imagine a B2B scheduling app. A Rails monolith two major versions behind, about 60 screens in use, 4 integrations (payments, SMS, calendar sync, a CSV feed from one partner), and a test suite that exists but fails in 30 percent of runs.
Scoring:
| Driver | Score | Why | |---|---|---| | Version distance | Medium | Two hops, each documented | | Test safety net | High | Flaky tests are worse than missing ones, because nobody trusts them | | Hidden coupling | Medium | One reporting script reads production tables directly | | Integration surface | Medium | Four integrations, one is a nightly file feed | | Knowledge loss | Low | The original developer is still reachable |
Now break it into workstreams, each with optimistic, likely and pessimistic effort expressed in engineer-weeks:
| Workstream | Optimistic | Likely | Pessimistic | |---|---|---|---| | Stabilize tests and add characterization coverage | 2 | 3 | 5 | | Hop 1 upgrade | 1 | 2 | 3 | | Hop 2 upgrade | 1 | 2 | 4 | | Integration re-verification | 1 | 1.5 | 3 | | Cutover, rollback plan, docs | 1 | 1.5 | 2 |
Summing gives 6 optimistic, 10 likely and 17 pessimistic engineer-weeks. Multiply by whatever blended weekly rate applies in your situation and you have a range of cost, not a point. The interesting part is the ratio: the pessimistic case is nearly three times the optimistic one, and most of that spread comes from the test net and the reporting script.
That tells you where to spend the first conversation. Ask the vendor how they would de-risk those two items before committing to a fixed figure.
Step 4: What to bring to the vendor call
- Your unit counts from Step 1
- Your driver scores and the reasoning
- Your range, and the two or three unknowns that stretch it
- Your non-negotiables: downtime tolerance, compliance constraints, the date something external forces
A good vendor will test your assumptions and may move your numbers. That is fine. You are not trying to win the estimate. You are trying to have a conversation where both sides are looking at the same facts.
Watch for the opposite pattern, a quote that arrives before anyone has asked about tests, integrations or version distance. That number is not based on your system.
Common mistakes
- Estimating only the code change. Testing, cutover, rollback and documentation are part of the job.
- Treating the estimate as a promise. It is a range that narrows as you learn. Plan to re-estimate after a short discovery phase.
- Forgetting to cost doing nothing. Unsupported versions carry security and hiring costs too. The range for the status quo is not zero.
If you would rather have a second pair of eyes on your numbers, the free 30-minute Triage is built for this: bring your unit counts and driver scores and we will tell you where we think the range is too tight or too wide. Or run the instant check first to see what a quick scan of your stack picks up.