“What will it cost?” is the first question on every call, and the honest answer at that point is always a range. That is not evasion. Two projects described in the same sentence — “a portal for our customers” — can differ by an order of magnitude depending on what sits behind the portal. What follows is what actually moves the number, and which of those levers are in your hands.
The Big Drivers
1. Integration surface
The application itself is often the cheaper half. Cost concentrates in the seams: every external system you must read from or write to adds authentication, error handling, retry logic, data mapping, rate limits, and a test environment you may not control. A standalone tool with its own database is comparatively cheap. The same tool that must stay in sync with an ERP, an accounting package and a scheduling system is a different project.
2. How well the current process is understood
If nobody can describe the current process end-to-end without disagreement, discovery is going to take longer — and discovery is where scope either gets controlled or quietly explodes. The single biggest cost saving available to most buyers is doing the internal work of agreeing what the process actually is before development starts.
3. Data migration
Migrating years of accumulated data from a legacy system is routinely underestimated. Real historical data contains duplicates, records that violate the rules the new system will enforce, free-text fields carrying meaning nobody documented, and edge cases created by staff working around limitations. Migration is rarely a script; it is a series of decisions about what to keep and what to correct.
4. Compliance, security and audit requirements
Handling payment data, health information or government data changes the engineering, the hosting, the testing and the documentation. These requirements are legitimate and they are not free. They also cost far less when they are known at the start than when they surface during a customer security review after launch.
5. Non-functional requirements
“It has to be fast” and “it has to be available” are cost decisions in disguise. Ninety-nine percent uptime and 99.99% uptime are different architectures with different price tags. Concurrency targets, offline capability, and mobile support all belong in the estimate conversation rather than the surprise conversation.
What You Can Actually Control
- Sequence. Shipping the highest-value slice first, then extending, costs less and de-risks more than building everything before anyone uses it.
- Scope discipline. Every “while we are in there” request has a price. Keeping a parked list and revisiting it after launch is cheaper than absorbing changes mid-build.
- Decision speed. Idle time waiting on an internal decision is real project cost. Naming one person who can decide is worth more than most tooling.
- Access. Delays getting credentials, test environments or sample data are common and entirely avoidable.
- Reuse. Existing licences and platforms you already pay for often cover part of the requirement. Building what you could configure is the most expensive kind of custom software.
What a Credible Estimate Looks Like
Be cautious of a fixed price quoted before anyone has looked at your systems. It is either padded to cover the unknowns, or it will be defended later through change requests. A more honest structure is a small paid discovery that produces a documented scope, an architecture, and a costed plan you own — and can take elsewhere if you want to.
Ongoing cost belongs in the conversation too. Software has a running cost after launch: hosting, dependency updates, security patches, browser and OS changes, and the changes your own business will ask for. A build quote without a support conversation is an incomplete picture.
The Short Version
Cost tracks complexity, not screens. Integrations, data, and compliance drive the number more than feature count does. The buyer-side levers — a clear process, fast decisions, disciplined scope, and shipping in slices — are usually worth more than any negotiation on rate.
Related service: Product engineering and custom software delivery
Where the Width of a Range Comes From
The spread in a range is not distributed evenly across the work. Most tasks estimate inside a fairly narrow band, and a handful of items carry nearly all the variance. Those items are almost always questions with a cheap answer and an expensive answer that nobody has checked yet: whether a field exists in the source system, whether an interface can be called at the volume you need, whether the data is in the state people believe it is in.
Most of those questions can be settled by a day or two of investigation rather than argued about in a meeting. Open the legacy database and count the rows that fail a required-field or foreign-key check. Pull a week of records from the vendor interface and see whether the field the design depends on is actually populated. Run the heaviest report against a copy at production data volume rather than a seed set of fifty rows. Each of these either collapses the range or confirms that the high end is real, and both results are worth having.
A range should also narrow as work proceeds. After two slices have shipped, the estimate for the third rests on observed throughput for this codebase and this team rather than on analogy to other projects. If the numbers are not moving once real work has been delivered, either the slices are too large to learn anything from or the remaining unknowns were never investigated. When you receive a range, ask which assumptions the low end depends on; that list is a work plan, and on most projects it contains some version of the following.
- Which system is authoritative for each shared record, and what the software should do when two of them disagree.
- Whether the legacy data can be read directly, or only through an export the incumbent vendor produces on request and charges for.
- What the reporting requirement is in concrete terms, since a nightly aggregate and a live view over the same records are different builds.
- How long records must be kept, and whether anything has to be deleted on request rather than archived.
- How many users sit outside your organization, since external accounts bring identity, permissions, and a support path that internal headcount does not predict.
Direction of Data Decides What an Integration Costs
Two integrations against the same system can differ by a factor of several, and the deciding factor is usually direction. A read-only pull is the cheap end: if it fails you retry, and nothing on the other side has been changed. Failure handling there is a question of freshness, not correctness.
Writing changes that. A request that times out may have succeeded anyway, so a retry can create a second order, a second invoice, or a second patient record unless the request carries an idempotency key that the remote system honors. Many interfaces do not offer one, in which case you build your own deduplication on a natural key, plus a way to find and repair the duplicates that get through it. Bidirectional sync is a further step again: for every field both sides can change you need a rule for who wins, a reconciliation job that finds the records which have drifted apart, and a person whose job it is to read what reconciliation reports.
The other half of the cost sits with the vendor and is fixed before you write a line of code. Rate limits stated per minute and per day constrain different things, and it is the daily cap that makes an initial load of several hundred thousand records take a week. An interface with no bulk endpoint has to be paged one page at a time inside that cap. Webhooks, where they exist, are commonly at-least-once delivery, so the receiver has to tolerate the same event twice; where they do not, you poll, and the polling interval is a decision about acceptable staleness with money on both sides of it. Every one of these is answerable by the vendor in writing before an estimate exists, and the answers belong in the estimate file:
- Published rate limits, and whether they apply per API key, per account, or per source address.
- Whether a bulk endpoint and a changed-since endpoint exist, for the initial load and for catching up after an outage.
- Whether webhooks are available, how deliveries are signed, and how long a failed delivery is retried before it is dropped.
- Which interface version you would build against, how long previous versions stay supported, and how breaking changes are announced.
- Whether the fields you need are exposed on the subscription tier the customer currently holds.
- Whether the license permits programmatic access at all, since some agreements restrict automated use or redistribution of the data.
Test Environments Are Where Integration Schedules Slip
A vendor sandbox is often a single shared instance per customer account, which means two testers writing at the same time interfere with each other's results in ways that look exactly like bugs in your code. Some sandboxes are reset on a fixed schedule and lose whatever was configured in them. And sandbox behavior is not production behavior: payment sandboxes approve nearly everything, some return fabricated values for fields that are populated differently in production, and sandbox rate limits are frequently lower than the live ones, so the first real load characteristic you meet is one you could not test.
Test data is the second half of the same problem. Sandboxes are usually seeded with a few tidy records that resemble nothing in production: no duplicates, no accented characters, no null in the field your mapping assumes is always filled. A copy of production data fixes the realism and creates a different problem, because that copy is now a system holding the same personal data as production under weaker access controls. The middle path is a subset that preserves the awkward shapes and replaces identifying values, which is a small build in itself and has to be re-run whenever the schema moves.
The gap between environments is its own category of defect. Something that works in staging and fails in production is usually not code: it is a certificate that was never installed on the second host, a name that resolves internally but not externally, an outbound firewall rule that exists in one place, or a secret someone set by hand and did not record. The diagnostic that separates these from real bugs is whether the identical build artifact is deployed in both places. If it is, the difference is configuration, and the fix is to move that configuration into something versioned rather than to start changing code.
What a Build Number Usually Leaves Out
A build price covers the building. It rarely covers the services the finished system consumes, which are normally contracted in the customer's own name: identity providers priced per monthly active user, mapping and geocoding calls priced per request, transactional email and SMS delivery, document generation, payment processing. Price these at expected volume and again at the volume you would have if the thing succeeds, because tiers do not always move in your favor. Message pricing is a routine surprise: SMS is billed per segment, a segment is 160 characters in the GSM-7 alphabet, and one character outside that alphabet, a curly quote or an emoji pasted out of a document, switches the entire message to UCS-2 and cuts the segment to 70.
The customer side of a launch is absent from the quote as well. Training, the stretch where staff run the old and the new process in parallel, rewriting procedures, and the raised support load in the first weeks are all real effort, and it lands on the same people who are answering the build team's questions.
Of everything in that category, the staff time to correct data during migration is the largest and the least planned for. It is work only the customer can do, because it turns on knowledge of the business rather than of the system; it sits outside the vendor's scope by definition; and it tends to arrive in the same weeks as acceptance testing, when those people are already committed. Other items in the same category are smaller and just as absent from a build number.
- App store developer accounts, their annual fees, and the review cycle that sits between a finished build and an available update.
- Licenses for anything the system depends on that the customer does not already hold, including the administrative tools staff will use daily.
- Cleanup inside the legacy system before an extraction is worth attempting at all.
- Decommissioning the old system: license termination, hardware or hosting kept alive in a read-only state, and the final export.
Run Cost Is Mostly Decided Before Launch
The largest lever on running cost is the count of distinct things that have to be kept current. Every runtime, database engine, and managed service in the design brings its own end-of-support date, and each one is a future upgrade that somebody plans, tests, and schedules around users. Two runtimes and one database generate a fraction of the calendar work of five and three, and that difference is settled at architecture time by people who are optimizing for something else entirely.
The second lever is spend nobody decided on. Non-production environments left running around the clock are a recurring charge with no user attached. Log and telemetry retention is priced by volume ingested and by how long it is kept, and the defaults are generous: a year of debug-level logs for a system that is only ever investigated within a week of an incident. Instances sized for a launch spike stay that size. Storage grows because nothing has a deletion policy. None of these are large on their own, which is why they survive.
Dependency maintenance costs whatever the shape of the tree makes it cost. A framework with a published long-term-support line and a stated support window lets patching be scheduled a year ahead; one that ships breaking changes on minor releases turns routine patching into rework. The count of direct dependencies matters less than the depth beneath them, because an advisory against a package you never chose still needs an upgrade path through everything that pulls it in, and that path sometimes does not exist yet. A few other choices look like implementation details when they are made and then set the run cost for years:
- Where authentication lives: delegated to an identity provider the customer already operates, or built into the application, which decides who owns password reset, lockout, and MFA support from then on.
- Whether a mobile build is native per platform or a single web application, since each native target adds a store review and its own operating system deprecation schedule.
- Whether backups have ever actually been restored, since an unverified backup is a cost that arrives all at once.
- Whether anyone is named to receive and act on security advisories for the stack, which makes patch cadence a staffing decision before it is a technical one.
Frequently Asked Questions
Can a fixed price ever be the right structure?
Yes, on a bounded piece of work whose unknowns have already been resolved. A discrete phase with a written definition of done, sitting on an architecture that has been settled, can be priced fixed because both sides can tell without argument whether it is finished. What makes it workable is not the size of the phase but the absence of open questions that could still change the architecture underneath it.
What has to exist before an estimate is worth anything?
A list of the systems involved with a named contact for each, and access to whatever documentation exists for them. A sample of the real data is worth more than a schema diagram, because the diagram describes what the system was designed to hold rather than what is in it. If a system is being replaced, an export covering one full business cycle, a month or a quarter depending on the process, is usually enough to see the shape of the problem.
Does building on a platform we already own always cost less than building from scratch?
It costs less right up to the point where the requirement stops matching what the platform models, and then it costs more. The test is where the customization lands: work built on the platform's supported extension points stays cheap, while overriding core behavior or keeping the real data in a side table outside the platform's model gives the savings back and turns every future platform upgrade into a project. This is worth settling before the build, because migrating off a platform you have been overriding is a second project rather than an adjustment.
Should the system run in our cloud account or the builder's?
Either works, and the difference is not mainly price. In the customer's own account the customer holds the billing relationship, sees actual usage, and keeps the environment whatever happens to the relationship, but the customer's own controls then apply to every change: identity policy, network rules, and whatever approval a new service requires. A vendor-held account moves faster at the start and is harder to take back later. Decide before the first environment is built, because moving accounts afterward means re-creating identity, DNS, certificates, and secrets, then re-testing everything that touched them.
What makes a change cheap after launch, and what makes it expensive?
Whether the change is data or code. A new rule row, a new option in a list, or a new email template needs no release and can be made by someone in the business; a change to the shape of the data, or to what a workflow means, touches migration, tests, and every integration that reads it. It is worth deciding during the build which categories of change will be configurable, since retrofitting configurability costs more than designing it in. Making everything configurable is its own expense, because a fully configurable application is a product that somebody now has to administer.
What do we have to decide, as opposed to provide?
How far back the migration goes, what happens to records that fail validation (corrected, excluded, or carried across with a flag), and how the cutover runs: a single switch, or a period with both systems live and someone responsible for keeping them consistent. You also have to name someone who can accept completed work, meaning sign it off rather than comment on it, because review that produces opinions but never an acceptance leaves the work in the same state as no review at all.
Is there a point where the numbers say do not build?
Yes, and it usually shows up as a small number of people doing an infrequent task. If a process runs a few times a month and the problem is inconsistency rather than volume, a written procedure plus a shared file with validation rules will hold for a long time and costs almost nothing to change. The signal that it has stopped holding is not that people are frustrated with it; it is that the workaround has acquired maintenance of its own, someone repairing the file every week, or the same records being retyped into a second system.