I Shipped Fewer Bugs After I Started Writing Specs — Here's the Framework
Last March, we had a billing incident that cost us three days. A rounding error in our invoice calculation was silently overcharging customers by fractions of a cent. Not enough for anyone to notice on a single invoice — but multiplied across thousands of transactions over two months, it added up. We had to issue refunds, write a post-mortem, and rebuild trust with a handful of enterprise customers who did the math.
The fix took 20 minutes. The actual bug was a Math.floor() where we needed Math.round() with banker's rounding.
Here's what stung: if anyone had written down "use banker's rounding for currency calculations" before writing the code, this never would have shipped. The developer who wrote it didn't know about our rounding convention. The reviewer didn't think to check. Nobody was wrong — the requirement just lived in one person's head and never made it to the keyboard.
That incident changed how our team builds software.
What spec-first actually means
Spec-first development is simple: before you write code, you write a short document describing what you're building, what you're not building, and how you'll know it works.
It's not waterfall. It's not Big Design Up Front. It's 10 minutes of structured thinking in a text file before you open your editor. Think of it as a conversation with your future self and your reviewers — except you have it before you've sunk 8 hours into an implementation.
The spec travels with the PR. It's a living artifact, not a bureaucratic gate.
A real example: CRM contact deduplication
One of our team members picked up a ticket to build contact deduplication for our internal CRM. On the surface, it seemed straightforward: find duplicate contacts, merge them. Here's the abbreviated spec she wrote before starting:
## Goal
Detect and merge duplicate contacts in the CRM based on email
and phone number matching.
## Non-goals
- Fuzzy name matching (phase 2)
- Automated merging without user confirmation
- Deduplication of company records
## Acceptance Criteria
- Two contacts with the same email (case-insensitive) are flagged as duplicates
- Two contacts with the same phone number (normalized to E.164) are flagged
- User sees a side-by-side comparison and chooses which fields to keep
- Merge preserves all activity history from both records
- Merge is reversible for 30 days
## Edge Cases
- Contact A has email X, Contact B has email X and email Y —
what happens to email Y after merge?
- Three-way duplicates: A matches B, B matches C, but A doesn't match C
- Contact with 500+ activities — does the merge UI choke?
- Merged contact is referenced in active automation workflows
That spec took about 12 minutes to write. But look at what it caught before a single line of code was written:
The three-way duplicate problem. Without the spec, the developer would have built a pairwise merge and discovered the transitive matching issue during QA — or worse, in production when a user tried to merge a chain of duplicates and got inconsistent results.
The automation workflow reference. Contacts linked to active automations needed special handling. That requirement wasn't in the ticket. It came out of the developer thinking through edge cases while writing the spec. Without it, merging a contact mid-automation would have broken running workflows.
The reversibility requirement. The original ticket said nothing about undo. But when you write "acceptance criteria," you start thinking about what happens when things go wrong. Thirty days of reversibility turned out to be a key requirement from the customer success team — the developer discovered this during a quick spec review before starting.
The template
After six months of iteration, our team settled on a lightweight template. Here's the version we use for any feature that takes more than a couple of hours:
# Feature: [Name]
**Author:** [Your name]
**Date:** [Today]
**Status:** Draft / In Review / Approved
## Goal
One paragraph. What are we building and why?
## Non-goals
What are we explicitly NOT doing? (This prevents scope creep.)
## Acceptance Criteria
Numbered list. How do we know this is done?
## Edge Cases
Bullet list. What weird inputs, states, or timing issues could break this?
## Rollout
How does this get to production? Feature flag? Percentage rollout?
Who monitors it?
That's it. Five sections. One page. The non-goals section alone has saved us more rework than any other practice we've adopted. When someone suggests "hey, we should also handle X" during implementation, we check the non-goals list. If X is there, the answer is "not this PR." If it's not there and it should be, we update the spec and have that conversation before writing more code.
I've published the full template with examples if you want to grab it.
Six months of results
We started using specs consistently in October 2025 across a team of eight engineers. Here's what changed by April 2026:
P1 incidents dropped from ~3/month to ~1/month. Not all of that is attributable to specs — we also improved our CI pipeline. But the incidents we did have were operational (infrastructure, scaling) rather than "we built the wrong thing" or "we missed an edge case."
Code review cycles got shorter. Average time from PR open to merge went from 2.1 days to 1.4 days. Reviewers stopped asking "what is this supposed to do?" because the spec was right there. Reviews focused on implementation quality instead of requirements discovery.
Junior engineers ramped faster. Two new hires in January were writing production specs within their second week. The specs gave them a structured way to demonstrate understanding before writing code — and gave senior engineers a concrete artifact to review instead of trying to evaluate whether someone "gets it" from a Slack conversation.
Scope creep became visible. When a feature grew beyond its original spec, we could point to the document and say "this is new scope — do we want to update the spec or save it for a follow-up?" Before specs, scope crept silently until the PR was twice the expected size.
The biggest surprise: specs made estimation better. When you force yourself to list acceptance criteria and edge cases upfront, you have a much clearer picture of the actual work. Our sprint velocity predictions got noticeably more accurate — not because we got better at estimating, but because we got better at understanding what we were estimating.
Getting started
If you've never written a spec before, don't try to change your whole team overnight. Start with yourself, on one feature. Pick something you're about to build that has at least a couple of moving parts. Spend 10 minutes filling in the template above. Share it with whoever would review your PR and ask: "Does this match your understanding?"
That one conversation will tell you whether spec-first development is worth adopting more broadly. In my experience, it always is.
I've written a complete guide to spec-first development that covers the philosophy, the process, and the common objections. And the template is free to download — take it, modify it, make it yours.
Daniel Marsh is a software engineer and the author of spec-coding.dev, a resource for teams adopting spec-first development.
Top comments (0)