Teams rarely struggle because nobody can write code. They struggle because several reasonable interpretations of the same request survive long enough to become separate implementations. A ticket says “add sharing.” Design imagines a public link, engineering imagines an invitation system, and QA imagines role-based access. Everyone moves quickly. Nobody moves together.
Spec-Driven Development, or SDD, addresses that gap. It makes the specification the stable contract between product intent, technical design, implementation, and verification. The spec is not ceremony placed in front of development. It is the smallest useful description of observable behavior that lets different people build and test the same thing.
01
What SDD changes
In a code-first workflow, the implementation often becomes the first precise description of the product. Ambiguity is resolved inside functions, database conditions, and interface states. By the time another person discovers those decisions, changing them is expensive.
SDD moves precision earlier. Before choosing component boundaries or storage details, the team describes what a user or another system can observe. A useful spec answers questions such as:
- Who can trigger the behavior, and under what conditions?
- What is the normal sequence and visible result?
- What changes in the system after success?
- What happens when data is empty, stale, duplicated, or unavailable?
- How can a reviewer objectively prove the rule was implemented?
A specification is ready when two developers can implement it independently and produce essentially the same user-visible behavior.
This does not mean specifying every internal detail. The product contract should be strict where behavior matters and deliberately open where implementation freedom is useful. A rule may require that a failed payment never grants access; it does not need to dictate a function name or folder structure.
02
The working loop
SDD is best understood as a feedback loop rather than a documentation phase. Each stage produces a clearer artifact and exposes gaps in the previous one.
Start with the problem, not the feature name
“Build notifications” is a solution-shaped label. It does not say who needs them, which event matters, or what outcome is expected. A stronger starting point is: “Workspace owners need to know when an import fails so they can correct the source file before the reporting deadline.” That sentence gives the spec somewhere to go.
Define the target user, desired outcome, current scope, explicit non-goals, known facts, and unresolved questions. Treat assumptions honestly. An unanswered product question belongs in a visible “to confirm” list, not disguised as a rule.
03
Write an executable specification
“Executable” does not require a special file format. It means each important rule can become a deterministic test or acceptance step. Plain language is often enough when it names the condition, action, and result precisely.
When a workspace owner retries a failed import, the system creates one new import attempt and keeps the previous attempt available in history. Repeated clicks while the retry is pending must not create additional attempts.
The example states the actor, trigger, state change, history requirement, and concurrency boundary. It leaves queue technology and database schema open. A developer can design it, and a tester can verify it without reverse-engineering intent.
Choose the smallest precise structure
Long prose is not always the clearest tool. Use a sequence for ordered behavior, a decision table for combinations of conditions, a state diagram for lifecycles, or a permission matrix for roles and resources. The goal is not visual decoration; it is to make contradictions and missing cases visible.
| Import state | User action | Expected result |
|---|---|---|
| Failed | Retry once | Create one pending attempt |
| Retry pending | Retry again | Keep the existing attempt |
| Completed | Retry | Reject and explain why |
Pay special attention to invariants: statements that must remain true in every state. “At most one active retry exists for an import” is more powerful than documenting the happy path alone because it guides UI state, API behavior, persistence, and testing.
04
Design only what the specification needs
Once behavior is stable, technical design translates the contract into a reliable system. It should explain boundaries, data lifecycle, interfaces, failure handling, security, compatibility, and the verification strategy. It should not become a tour of every file the team expects to create.
A good design can point back to the rules that force a decision. If the spec requires idempotent retries, the design explains the idempotency key and uniqueness boundary. If the spec requires an audit trail, the design explains what is stored and retained. Every substantial mechanism should earn its place by supporting a stated behavior or operational risk.
Design check
If a technical decision cannot be connected to a rule, constraint, or measured risk, it may be premature. If a rule has no credible implementation path, the design has exposed a spec gap—return to the specification before coding onward.
05
Trace rules to tasks and evidence
Large plans fail when tasks become detached from outcomes. Break the design into changes that can be implemented and verified independently. Each meaningful task should identify the rules it satisfies, the modules it affects, and the evidence required for completion.
Rule R-IMP-003 → Task: implement idempotent retry endpoint → Module: import service → Evidence: integration test IMP-RETRY-02This modest trace is enough to improve review. A changed rule reveals affected tasks and tests. A failing test points back to the intended behavior. An implementation with no rule invites the question: is it necessary, or is the spec incomplete?
Implement in small closed loops
- Read the relevant rule and its acceptance criteria.
- Make the smallest implementation change that satisfies it.
- Run the closest useful verification.
- Compare the observed result with the spec—not with the current code.
When something fails, classify the failure. Code may contradict a clear spec. The spec may contain ambiguity. The design may not support the required behavior. Or new information may represent a genuine requirement change. These cases demand different responses; calling all of them “bugs” hides important product decisions.
06
Put SDD into practice without creating bureaucracy
SDD is not measured by document count. A small, low-risk feature may need a single page containing scope, five rules, and a handful of acceptance scenarios. A complex subscription lifecycle may need separate product, specification, design, and task artifacts. Use the minimum structure that preserves shared understanding.
A practical starting kit
- One problem statement that names the user and desired outcome.
- A scope boundary with explicit non-goals.
- Stable rule IDs only for behavior worth tracing.
- Acceptance scenarios for normal, failure, and boundary behavior.
- A short design note for decisions with meaningful tradeoffs.
- Evidence linked back to the rules it proves.
Common failure modes
- The spec repeats the interface
- A list of buttons and fields says little about behavior. Describe triggers, state changes, permissions, and outcomes.
- The spec dictates internals
- Product rules should not freeze function names, SQL, or component trees unless those choices affect an external contract.
- Tests define the product retroactively
- A passing test proves only that code matches the test. First decide whether the test expresses the intended rule.
- The spec stops changing
- Production feedback is new evidence. Feed it into the next specification cycle instead of layering undocumented exceptions into code.
Start with one feature that currently generates repeated clarification. Write the observable rules before the implementation discussion. Ask someone else to explain the behavior back to you. If their interpretation differs, improve the spec while the cost is still a sentence—not a migration.
The deepest benefit of SDD is not more documentation. It is a shorter distance between what a team intends, what it builds, and what it can prove. Code remains the running system. Tests remain the evidence. The specification gives both a shared meaning.
