Skip to content
Epic Software Labs
All articles

Delivery

How we scope a build so it does not run over

The two-page specification format we use before writing code: outcomes, constraints, non-goals and acceptance criteria — and why scoping is now the constraint.

10 min readEpic Software Labs

Key takeaways

  • Now that implementation is fast, the binding constraint on delivery is decision-making. Scoping is where projects are won or lost.
  • A useful specification fits on two pages and states outcomes, constraints, non-goals and how you will know it worked.
  • Non-goals are the most valuable section, because they are the only part that prevents scope from expanding silently.
  • Every milestone ends with something deployed. A milestone with no running artefact is a status update wearing a costume.

Software project scoping fails for the same reason it always has: nobody agreed precisely enough what was being built.

That has become both less forgivable and more consequential. When implementation took months, a vague brief got corrected by the slow drip of demos and conversations along the way. When implementation takes days, a vague brief produces a finished, confident, wrong thing before anyone has had the corrective conversation.

So we spend disproportionate time on the document that comes first.

Why scoping documents usually fail

Most companies already have some version of a scoping process, and most of those produce documents nobody uses once building starts. Understanding why is useful before adopting a new format, because the same failure will recur without it.

They are written to be comprehensive rather than decisive. A requirements document that tries to anticipate everything ends up listing the obvious ninety percent everyone already agreed on, and glossing over the contested ten percent that actually causes disputes later — because the contested parts are uncomfortable to pin down and comprehensive documents let you avoid pinning anything down specifically.

They separate "what" from "what not." A features list without an explicit exclusions list looks complete and is not. Every reader fills in the gaps with their own assumption about what is obviously included, and those assumptions do not match.

They are approved once and never referenced again. A document that lives in a shared drive and gets signed off in a kickoff meeting has no mechanism for staying true. The format below is deliberately short enough to be re-read at each milestone boundary, which is the only way a specification stays a living reference rather than an artefact of a meeting that happened once.

The specification

Two pages. Five sections. Written before any code exists, and signed off by whoever gets to say the project succeeded.

1. Outcome

One paragraph, in the language of the people who will use it. Not "build a notifications service" but "an operations manager finds out within two minutes when a shipment misses its window, without watching a dashboard."

If this paragraph mentions a technology, it is written at the wrong level.

2. Constraints

What is fixed and not up for negotiation. Existing systems that must be integrated with. Compliance requirements. Performance floors. Team skills the result has to be maintainable by. Budget ceilings, including runtime cost, which is frequently forgotten until the first bill.

3. Non-goals

The most valuable section in the document, and the one people most often skip.

Everything a reasonable reader might assume is included but is not: the adjacent feature, the second user type, the integration that would be natural to add, the edge cases consciously deferred. Written explicitly, because unstated exclusions get quietly re-included by whoever is closest to the code.

4. Acceptance

How we will know it works, stated concretely enough that two people would agree on whether it had happened. Usually a handful of scenarios in plain language, which become the tests.

This section is what turns "done" from a judgement call into an observation.

5. Milestones

Each one ends with something deployed and usable. Not "backend complete" — a working slice, narrow but real.

The discipline here is that a milestone with no running artefact is not a milestone. It is a status update, and it hides risk rather than reducing it.

A worked example, in full

Templates are unconvincing without a real one attached, so here is a specification at the actual size we send clients — for a feature we would genuinely scope this way.

Outcome. A logistics manager can see, without opening a separate tool, which of today's deliveries are at risk of missing their window — defined as more than fifteen minutes behind the carrier's own estimate — so they can call the customer before the customer calls them.

Constraints. Delivery estimates come from the existing carrier API, which updates every ten minutes and occasionally goes stale for up to an hour during regional outages. Must run inside the current operations dashboard, not a new tool. Needs to work for the eighteen managers using the system today, with headroom to two hundred. No new database — this reads from data already being ingested for another feature.

Non-goals. Not predicting delays before they are reported by the carrier — this is a real-time status view, not a forecasting model. Not covering the two smaller carriers still integrated by email, which come in a later milestone. Not building customer-facing notifications; this is an internal tool for the operations team only. Not handling delivery types outside standard parcel — freight is excluded.

Acceptance. Given a delivery whose carrier estimate has passed by more than fifteen minutes with no delivery confirmation, it appears in a filtered "at risk" view within one refresh cycle. Given carrier data older than sixty minutes, the delivery shows a stale-data indicator rather than a false risk flag. Given a delivery that arrives on time, it never appears in the at-risk view, checked against a week of historical data before sign-off.

Milestones. Week one: risk calculation and a read-only list view behind a feature flag, tested against last month's real delivery data. Week two: filtering, sorting, and the stale-data indicator, rolled out to five managers. Week three: full rollout, with the flag removed once a week of use shows no false positives above 2%.

Read that back and notice what carries the weight. The acceptance criteria are checkable by someone who was not in the room when the spec was written. The non-goals section removes four things a reasonable engineer might otherwise have built without being asked — predictive delay modelling, email-carrier support, customer notifications, freight handling — each of which would have doubled the milestone on its own.

This is also, not incidentally, the format that turns into a milestone plan with a fixed price attached. Once acceptance criteria exist, estimating the work behind them is a much narrower problem than estimating "add delivery risk tracking to the dashboard."

Failure modes we see repeatedly

Four patterns account for most of the scoping failures we get called in to fix, roughly in order of frequency.

The outcome is written as a feature list. "Add user roles, an audit log, and email notifications" is not an outcome — it is three unscoped projects wearing one sentence. Each needs its own non-goals, its own acceptance criteria, and probably its own milestone. Bundling them under one heading is how a two-week estimate becomes an eight-week one, discovered in week three.

Non-goals get written after the fact, if at all. The temptation is to skip straight to what is included, because that feels like the productive part. But the section that actually prevents overrun is the one stating what is deliberately excluded — and writing it after implementation has started means you are documenting decisions instead of making them, which is a different and much less useful exercise.

Acceptance criteria describe the implementation instead of the outcome. "The API returns a 200 with the correct payload" is a test of code, not of the spec. A good acceptance criterion is checkable by someone who has never seen the code: "the manager sees the delayed delivery within one refresh cycle." If a non-technical stakeholder cannot judge whether a criterion has been met by using the product, it is testing the wrong layer.

Milestones are sized by calendar time instead of by deployable slices. "Two weeks of backend work" is not a milestone; it produces nothing anyone can look at, which means the first opportunity to discover a misunderstanding is at the two-week mark rather than continuously. A milestone should be defined by what becomes true in production, not by how much time has elapsed.

What happens when scope needs to change mid-milestone

Fixed scope inside a milestone does not mean scope is frozen forever — it means a change is a visible decision rather than something that accumulates silently. The mechanism matters more than the policy.

When something genuinely needs to change inside a live milestone, we do three things rather than either refusing the change or absorbing it silently:

  1. Name what the change actually costs — in the same units as the original estimate, not as a vague "this will take a bit longer." If a milestone was three days and the addition is genuinely half a day, say half a day.
  2. Ask what it displaces. A milestone with a fixed end date and added scope has to give something up — either the timeline moves, or something else in the milestone gets deferred to the next one. Naming the trade-off is what keeps the conversation honest.
  3. Write the change into the spec, not just into a chat thread. The non-goals list and the acceptance criteria both get updated. A specification that stops matching what was actually built is worse than no specification, because people keep trusting it.

The failure mode this avoids is the one every engineer recognises: scope creep that nobody agreed to, discovered only when the milestone runs long and nobody can explain exactly why.

Why this matters more now

The old constraint was engineering hours, so the rational thing was to spend planning time proportional to build time. Two days of scoping for a three-month build felt about right.

That ratio has inverted. When a well-specified two-week feature can be substantially built in two days, the two days spent getting the specification right are no longer overhead — they are the majority of the value-adding work, and the place where the outcome is actually determined.

Fixed scope development versus open-ended engagements

There is a broader question behind all of this: should a software engagement be scoped and priced as a series of fixed commitments, or run as an open-ended relationship where scope flexes continuously?

Fixed scope development — the approach this whole piece describes — commits to a specific, written outcome for a specific milestone, priced in advance. It works because the commitment period is short. A four-week milestone can be genuinely fixed because four weeks is short enough that unknowns stay manageable; a six-month one cannot, because too much will be learned along the way for the original scope to remain the right scope.

Open-ended, time-and-materials engagements flex continuously and never force the scoping discipline described above, because there is no moment where "what exactly are we building" has to be answered precisely. This works for genuinely exploratory work — research, early product discovery, anything where the team does not yet know what the right feature is. It works badly for well-understood work, because without a forcing function, scope drifts gently in whatever direction feels most urgent that week, and six months later there is a lot of activity and no clear artefact to show for it.

Our approach is to run every engagement as a sequence of fixed-scope milestones — short enough that "fixed" is realistic, sequential enough that learning from one informs the scope of the next. This gets the certainty of fixed pricing without pretending that a whole project's scope was knowable on day one. The alternative — a single fixed price for a six-month build — is usually fiction dressed as certainty: everyone signs it knowing scope will change, and then spends the engagement negotiating change requests instead of building.

What we do when scope is genuinely unknown

Sometimes the honest answer is that nobody knows what should be built yet. Writing a specification for that is theatre.

In that case the first engagement is explicitly a scoping engagement — one to two weeks, with a written specification, an architecture outline and a milestone plan as the deliverable. It is priced separately and structured so the output is useful regardless of who builds it, including if that turns out not to be us.

That is not generosity. A project that starts from a real specification is one we can price honestly and deliver on time, and those are the only kind worth taking.

How this connects to the rest of delivery

Scoping does not stand alone — it is the input that makes everything downstream honest. The method for deciding what actually belongs in a first release, described in what to build first, is what fills in the "outcome" and "non-goals" sections above; scoping without that groundwork produces a well-formatted document describing the wrong thing.

The output also determines how the engagement should be priced. A specification with genuine acceptance criteria is what makes fixed-price milestones honest rather than a guess with a signature on it — you cannot fix a price against ambiguity, and a two-page spec is the mechanism that removes it. Teams that skip this step and go straight to a quote are pricing the document they wish they had, not the one they have.

The habit worth building

Scoping well is a habit more than a technique, and like most habits it compounds. A team that writes a real two-page specification for its fifth project does it faster and better than for its first, because the muscle of separating outcome from implementation, and non-goals from included scope, gets easier with practice. The investment in doing this properly on the next project pays forward onto every project after it — which is the strongest argument for building the discipline now rather than treating it as overhead to skip when a deadline feels tight.

In one sentence

A specification is not bureaucracy — it is the cheapest place in the entire project to resolve an ambiguity, and the only question is whether you resolve it there or pay for it later, at a much worse rate.

A practical next step

For the next piece of work you commission or approve, write the five sections of the specification format above yourself, before involving anyone else, even in rough form. The act of trying to fill in "non-goals" specifically tends to surface the assumptions that would otherwise have caused a dispute three weeks into the build — better to find them alone at a desk than in a difficult conversation mid-project.

Frequently asked questions

How long should a specification be?

Two pages. If it needs more, the milestone is too large and should be split. Long specifications are usually a symptom of unresolved decisions being deferred into detail rather than made.

What goes in a non-goals section?

Everything a reasonable person might assume is included but is not — the adjacent features, the edge cases you have consciously decided to handle later, the integrations that come in a future milestone. It is the section that stops scope from growing without anyone deciding it should.

Does fixed-scope work when requirements change?

Yes, provided the milestones are short. Fixing scope for four weeks is realistic; fixing it for six months is fiction. Changes between milestones are normal and expected — the structure exists so a change is a visible decision rather than a quiet accumulation.

What is a software specification template that actually works?

The one that works is short: outcome, constraints, non-goals, acceptance criteria, milestones, on two pages. Longer templates with dozens of fields get filled in mechanically and stop forcing real decisions. The value of a specification is in the questions it makes you answer before building, not in its completeness as a document — a two-page spec that resolves genuine ambiguity beats a twenty-page one that restates the obvious.

Who should write the specification — the client or the development team?

Both, in sequence. The outcome and constraints are the client's to state, because only they know what the business actually needs. The non-goals and acceptance criteria are best drafted by the development team and reviewed by the client, because engineers are better at spotting the ambiguous edge cases a non-technical stakeholder would not think to exclude. Writing it entirely on one side produces a document that is either commercially naive or technically presumptuous.

How do you scope a project when the client does not know what they want yet?

You do not write a full specification — you run a scoping engagement instead, priced and delivered separately from the build. Its output is the same document format, but arriving at it is the explicit goal of the engagement rather than a formality before one. This is described in more detail below; it is a different piece of work with a different price, not a discount version of scoping a known project.

References & further reading

  1. [1]
  2. [2]
  3. [3]