The dominant factor is rarely technology — it is how much is still undecided. Every open question in the specification is converted into a contingency inside the number you receive. A team that has no visibility into what happens on the unhappy path must assume the worst. Spending a week on a proper discovery frequently cuts the final cost far more than haggling over hourly rates.
Connections to other systems are the next major multiplier. A feature that touches only your own data is predictable; the same feature talking to a legacy ERP is a different problem. The effort hides in the other system: rate limits and sandbox access, waiting on someone else's team, fields that mean something different on each side. Ask the estimator to break integrations out as separate items, as this is the usual source of overruns.
Quality attributes can easily double the estimate. A tool used by a small internal team costs far less than the same feature set handling a hundred thousand users. Compliance work, uptime targets, load handling, code audit services logging and accessibility add real engineering time. State them early or you can expect them priced as extras.
The team you are quoted matters. An hourly rate tells you little on its own: a senior engineer at a higher rate frequently turns out to be less expensive in the end than two juniors who require constant review. Check too what else appears on the invoice: project management, testing, release engineering and UX design are legitimate costs, but these should be named rather than hidden inside a blended rate.
The quoted figure is rarely the total cost. Plan for hosting, paid APIs, difference between monolith and microservices monitoring and a maintenance allowance for every year the software runs. A common working assumption is that software in active use requires a noticeable fraction of its original build cost annually simply to stay current. Treating the launch as the finish line has always been the classic mistake.