Write the spec before you write the code
Why we compress ambiguity into acceptance criteria before a single line ships — and how it saves weeks downstream.
The most expensive bugs aren't in the code. They're in the brief — the quiet assumption that two people understood the same word to mean the same thing. By the time that surfaces in a build, you've already paid for it twice: once to write the wrong thing, once to tear it out.
A written spec with acceptance criteria is the cheapest place to discover you're building the wrong thing. It's a paragraph, not a sprint. When you force a feature down to "this is done when X, Y, and Z are true," the disagreements that would have detonated in week six show up in the first conversation instead — where they cost a sentence to fix.
Ambiguity compounds
Vague scope doesn't stay vague. It hardens into code, and code is opinionated. Every unstated assumption gets resolved by whoever happens to be typing, in the direction that's easiest at that moment. Multiply that across a feature and you get a product that technically matches the brief and satisfies no one. The spec is where you spend ambiguity down before it compounds.
Acceptance criteria are a forcing function
"Add validation" is not a spec; it's a wish. "Reject submissions with an empty email, show an inline error, and keep the rest of the form filled" is a spec — you can build it, and you can prove it's done. The act of writing criteria that precise is what flushes out the questions nobody asked. Half the value of the document is in the arguments you have while writing it.
Notice what the second version does that the first doesn't: it names the failure case, the behavior, and the boundary. "Reject submissions with an empty email" tells you what happens when the data is wrong. "Show an inline error" tells you where. "Keep the rest of the form filled" tells you what not to break. None of that is in "add validation," but all of it is in someone's head — usually a different head than the one writing the code. The spec drags it into the open while it's still free to change.
The disagreements are the point
Most people treat the spec as a deliverable: a document you produce, approve, and file. That's backwards. The document is a byproduct. The real output is the set of disagreements it surfaces.
When you write "this is done when X, Y, and Z are true," someone will say "wait, what about the case where the user has no account yet?" — and now you've found, for the price of a sentence, a branch that would otherwise have been discovered halfway through the build, by a developer guessing, in whatever direction was easiest at 4 p.m. on a Thursday. Every one of those guesses is a small bet placed with your money. The spec is where you stop betting and start deciding.
This is why we don't write specs alone and email them around for sign-off. We write them with the people who own the outcome, out loud, because the argument is the work. A spec that produced no argument almost certainly hid the hard questions instead of answering them.
What a good spec actually contains
A spec is not a requirements document and it's not a design doc. It's shorter and meaner than both. For a single feature, we want four things on the page:
- The intent. One sentence on what this is for and who it's for. If you can't write it, you don't understand the feature yet — and neither will the person building it.
- The acceptance criteria. The concrete, testable conditions that make this "done." Not "works well" — "completes in under two seconds on a mid-range phone," "survives a dropped connection mid-submit."
- The edges. What happens when the input is empty, the network dies, the user is on their third attempt, the list has ten thousand rows instead of ten. Edges are where products actually break, and they're invisible in a happy-path mockup.
- The explicit non-goals. What this feature is not doing, on purpose, in this pass. Naming the non-goals is how you stop scope from creeping in through the side door later.
That's it. No Gantt chart, no forty-page appendix. If the spec for a feature runs longer than the feature deserves, that's a signal the feature is too big and should be split — which is itself a useful thing to learn before you build it.
"We don't have time for a spec"
We hear this, and it's exactly backwards. The spec is the fast path, not the slow one. The slow path is building the wrong thing confidently, shipping it, watching it confuse people, and then doing the archaeology to figure out which assumption was wrong before you can fix it.
A spec costs an afternoon. Tearing out a feature that was built against the wrong assumption costs a sprint, plus the trust you spent shipping it. The teams that feel too busy to write the spec are usually busy because they didn't. Speed comes from not doing the work twice.
There's a real version of the objection, though: some things genuinely can't be specified up front because you don't yet know enough to decide. That's fine — but then the honest spec says "we will build the smallest thing that lets us learn X, and decide the rest after." That's still a spec. It's just a spec about what you're going to learn instead of what you're going to build. The enemy isn't uncertainty; it's pretending the uncertainty isn't there.
Specs and AI make each other better
This matters more now, not less. When a coding assistant can generate a plausible implementation from a vague prompt in seconds, the vague prompt becomes the bottleneck — the model will happily build the wrong thing faster than ever. A precise spec is the difference between an assistant that accelerates you and one that generates confident, well-formatted mistakes at scale. The acceptance criteria you'd write for a teammate are the same ones that keep a model honest. Garbage in, garbage out — just quicker.
This is the Shape work
Compressing ambiguity into a written spec with acceptance criteria is the first thing we do on any engagement — it's the Shape part of how we work. It isn't bureaucracy and it isn't a gate. It's the cheapest insurance you can buy against building the wrong thing beautifully, and it's the one step most likely to be skipped under pressure.
So we don't skip it. We write the intent, the criteria, the edges, and the non-goals; we have the argument while it's still cheap; and we don't open an editor until we can say, in plain language, what done looks like. Then — and only then — we write the code. It's not slower. It's the reason the thing ships once.