product
Quoting a fixed price without going broke
We put the price on the page before the work starts, and a wrong estimate is our cost rather than a change order. That is a short promise and an expensive one, and nearly all of the work of keeping it happens before anyone opens an editor.
Fixed price has a poor reputation because it gets sold as a transfer of risk and then gets run as an argument. The argument is rarely about money. It is about a scope document that was never precise enough to be wrong. What follows is the mechanical version of how we make the promise survivable, including the parts that go badly.
A number attaches to a sentence you can finish
A scope item is priceable when two engineers who have never spoken to each other would build the same thing from it. That is the entire test, and it is harsher than it sounds.
Compare two lines. The first is Export to CSV. The second: a signed-in user with the finance role can export the transaction list for a chosen date range as CSV, with the same columns the table shows; up to 50,000 rows the file is produced inside the request, and above that the export is queued and the user gets a link by email. The first line is a topic. The second is a thing you can finish, and a thing you can be wrong about in a bounded way. Only one of them can carry a number.
So every item in our scope document carries four parts:
- An actor. Who does this. Roles nobody has enumerated are features nobody has counted.
- A trigger. What starts it, including the case where a system starts it rather than a person.
- An observable end state. Something a second person could check without asking the author what they meant.
- A boundary. The nearest thing it is not. Export is not scheduled export. Search is not fuzzy search. Notifications are not notification preferences.
The exclusion list does the quiet work
The section listing what is not included should be longer than anyone finds comfortable. Every exclusion is a conversation you have now, cheaply, instead of in week five, expensively. When a client reads that list and says that one is obviously needed, you have found a real requirement for the price of a line of text. When they read it and say nothing, you have a shared, dated record of the boundary.
A fixed scope is not a fixed outcome
We commit to building the stated thing, deployed, in the client's repository, meeting the acceptance conditions written beside it. We do not commit to it working in their market. Those are two different promises and only one of them is ours to make.
Being blunt about that at the start decides what happens later, when a feature lands and the number it was supposed to move does not move. If the two promises were blurred at signing, that moment becomes a conversation about the invoice. If they were separated, it becomes a conversation about the product, which is the useful one. The build being correct and the bet being wrong are allowed to be true at the same time.
Unknowns you resolve, unknowns you price
Not every unknown is the same kind of unknown, and a buffer only works on one kind. Variance is a distribution you could describe out loud: this usually takes three days, occasionally six, and there is a version of the bad case you can picture. Ignorance is the absence of that picture. You cannot buffer ignorance, because you do not know what you are multiplying.
These get resolved before a number goes on the page, every time:
- A third-party system nobody on the team has touched. Documentation is not evidence. Credentials in hand and one real call made against the sandbox is evidence.
- Data you have not seen. A spreadsheet described as clean holds three date formats and a column where someone typed a note into a numeric field. Migration cost is set by the worst two percent of rows, not by the shape of the other ninety-eight.
- Approvals outside the client's control. App store review, a security questionnaire from their largest customer, a partner rate limit that gets raised by email when someone feels like it.
- A performance requirement attached to a volume nobody has measured. Fast enough is a number with a row count beside it, or it is nothing.
What does get a buffer is work whose shape is known and whose depth varies: device and browser quirks in an interface pattern we have built before, a schema migration where we have already read the schema, rounds of copy review when the number of rounds is written down. Those get a buffer because we can describe the bad case.
When an unknown resists that treatment, we sell a discovery week instead of a quote. It is priced separately, it produces a written finding and usually a small working spike, and it ends with either a fixed price or the plain statement that this is research. Research is billed by time. A studio that fixed-prices research is either lucky or about to stop being lucky.
Change requests against a running build
Renegotiation turns ugly for a structural reason. The change arrives late, bundled with five others, argued against a document rather than a thing. A deployed build every Friday removes most of that, because the change is discussed against software the client used yesterday.
Requests then sort into three buckets:
- We misread the scope. The item said one thing and we built another. We fix it at no cost. This is the case the fixed price exists to cover, and it should be the most common one.
- A swap of similar size. Something in scope has become less valuable than something outside it. We swap them and write the swap down.
- New scope. Priced as its own small fixed item, agreed before it starts, under the same rules as the original quote.
The second bucket is where relationships quietly go wrong, because a swap agreed in a call is remembered differently by both sides three months later. We keep the scope document in the client's repository as a file, and changes to it arrive as pull requests. The record of what was agreed, and when, is then git log, and nobody has to take anyone's word for it. It also has the side effect that the scope document gets read, since it turns up in review.
What a wrong estimate actually costs us
The direct cost is the overrun in engineer weeks, and no contract structure removes it. The indirect cost is larger. On a small team, a project running three weeks long moves the start of the next one by three weeks. The real price of a bad estimate is the overrun plus the gap it opens behind it.
Estimate error is also not symmetric. You are wrong long far more often than wrong short, because the things you forgot are always extra work and never extra free time. Most engineers know the direction of their own error and have never kept the record that would tell them its size.
Four things keep it survivable:
- Keep the priced unit small. We price a slice measured in weeks. A quarter-long fixed price is a bet on a forecast wearing the clothes of an estimate.
- Never fix-price two novel things at once. One unfamiliar integration in an engagement is an estimate. Two is a research project with a number on it.
- Compare estimate to actual per slice, weekly. An engagement that will overrun by forty percent is usually visible in week two as a forty percent overrun on the first slice. The information exists early. The mistake is not looking at it until the end.
- Quote fewer things at the same time. Utilization and estimate accuracy trade against each other. A studio with no slack has no room to absorb its own mistakes, which is the whole point of the model.
Work that should not carry a fixed price
Declining to fix-price something is not a failure of nerve. Some work has no shape a price can hold.
- Research. If the question is whether something is possible, the answer is the deliverable, and the answer might be no.
- Work gated on someone else's calendar. An integration with a partner whose API ships soon is priced against a date you do not control.
- Open-ended performance work. Make it faster is not scope. Return this query in under 200ms at the current row count is scope.
- Maintenance and on-call. That is a capacity commitment, and capacity is sold by time. Pricing it as scope means pricing incidents that have not happened yet.
- A rewrite of a system nobody can currently describe. Price the archaeology as its own piece of work. Price the rewrite afterwards, if the archaeology produced a description.
- Any engagement with no named decision-maker. Review latency is a real cost sitting entirely on the other side of the table. Ask who signs off before quoting, and treat a vague answer as a finding rather than a detail.
Our rule is short. Attach a number to a piece of work only when you can write the sentence that says it is finished, and only when being wrong by half would be irritating rather than fatal. If the first condition fails, you do not have scope yet, you have a topic. If the second fails, the piece is too large and should be cut before it is priced. Everything else is a discovery week or an hourly rate, and saying so at the start costs one uncomfortable conversation. Not saying so costs the project.