Technical spec: the three readers your one document has
Table of contents
You have been asked to write a technical spec. So you open the team wiki to find one to copy.
There is nothing there. Or worse, there is one: forty pages about a service that got cancelled in 2024, written by someone who left, with a section header called "Non-Functional Requirements Matrix" that is empty.
This is the most common way engineers meet the technical spec. Not as a practice they were taught, but as a task handed to them with no house format, no example, and no clear idea of what "good" looks like. The guidance you find when you search is a list of seven to twelve sections you should include. It is never a list of the ones you should leave out, and it never says who the document is for.
That second question is the one that decides whether a spec works. A technical spec has three readers with genuinely incompatible needs, and most specs are written as one undifferentiated block that serves none of them.
What is a technical spec?
A technical spec (also called a tech spec, a spec document, or a technical specification document) is a written plan for how a specific piece of software will be built, produced before the implementation starts, so the approach can be reviewed and argued with while changing it is still cheap.
It states the problem, the proposed approach, the parts of the system that will change, the things that could go wrong, and what was deliberately not done. That last part carries more weight than it looks: on a rebuild, the "deliberately not done" list is your main brake on the urge to over-build version two, cramming every deferred idea into the successor. It is not a description of a system that exists. That distinction matters more than any section list, and it is where most of the confusion around the term comes from. The same plan-before-you-build discipline is what spec-driven development hands to an AI coding agent instead of a teammate, and it is half of how to work with a coding agent at all.
Note that "technical spec" means slightly different things at different companies. At some it is the engineering half of a product plan, written after a product requirements document. At others it is the whole design conversation and the term "design doc" is used interchangeably. Nobody is wrong. What matters is that your team agrees which document decides what, and that the decision is written down somewhere other than someone's memory.
The three readers your technical spec has
The same file gets read three times, by three people, at three points in time, and each of them wants something the others do not care about.
| Reader | Reads it when | Wants to know | Fails if the spec |
|---|---|---|---|
| The reviewer | This week, once, probably in 20 minutes | Is this approach wrong, and where | Is too long to finish, or too vague to disagree with |
| The implementer | Over the next few weeks, repeatedly | What exactly am I changing, and in what order | Skips the boring specifics: names, endpoints, migration order |
| The inheritor | In a year, once, under pressure | Why is it built like this, and what else was considered | Lists only the chosen approach, with no rejected alternatives |
Notice that the reviewer and the implementer want opposite things. The reviewer wants the document short enough to actually read. The implementer wants the migration order, the exact table names, and the rollback command. They aren't the same document. Trying to serve both in one continuous narrative produces a document that is too long for one and too vague for the other.
The fix is not a longer spec. It is a spec that separates the argument from the instructions, so the reviewer can read the first part and stop. Surfacing the load-bearing decision also protects it from the pull toward arguing the trivia: a reviewer who can find the choice that matters is less likely to spend the review on variable names.
Start writing in Scribelet if you want a place to keep specs where the claims in them get checked later. More on that at the end.
What each reader needs you to write
The reviewer wants sentences they can disagree with
The single most common defect in a technical spec is that it can't be argued with. "We will use a queue to decouple the services" is not a claim. Nobody can disagree with it, because it doesn't say which queue, what happens when it backs up, or what the ordering guarantee is.
Compare that to: "We will use SQS FIFO with a 5 minute visibility timeout. Messages are ordered per customer, not globally. If the consumer falls behind by more than 10,000 messages, we page rather than shed load."
That version can be wrong. A reviewer can point at the second sentence and say the ordering guarantee will not hold for the batch importer. That is what a review is for, and a spec that cannot produce that sentence has wasted everyone's twenty minutes.
The argument does not go away when the sentence is vague. It just moves to a more expensive room.
The IETF made this concrete decades ago. RFC 2119 defines MUST, SHOULD, and MAY as distinct requirement levels precisely so that a reader can tell a hard constraint from a preference. You don't need to adopt the capitalised keywords, but you do need the distinction. "The service should retry" and "the service must retry" describe different systems, and reviewers will assume whichever one is convenient unless you say.
The implementer wants the boring parts
The reviewer is gone by week two. Now the document has one job: stop the person building it from having to guess.
That means the parts that feel too obvious to write down. The name of the new table. The order of the migration relative to the deploy. Which feature flag gates it. What the rollback actually is, in a form specific enough that someone can run it at a bad time.
This is also where most specs quietly go wrong, because these are the details most likely to change during implementation. The decision gets made in a pull request comment, the code ships, and the spec still describes the plan rather than the thing. If that sounds familiar, it is the same failure mode covered in why engineering docs go stale.
The inheritor wants to know what was rejected
A year later, someone is looking at this system and wondering why it is built the strange way it is built. The chosen approach is visible in the code. What is not visible is the three approaches that were considered and dropped, and why.
Rejected alternatives are the highest-value and first-cut section of any spec. Write them as one honest sentence each: what it was, and the specific reason it lost. "Postgres LISTEN/NOTIFY: dropped because notifications are lost if no listener is connected, and we need delivery across a deploy" is worth more in a year than four pages of the design that won. It is also your first defense against feature creep: a feature you rejected with the reason written down cannot quietly return the next time someone asks for it.
When a decision outlives the spec that contained it, promote it. That is what an ADR template you can copy exists for: a decision record survives the project, and a spec does not.
A technical spec template
Copy this. It is short on purpose, and the fields most templates omit are the last three.
## Technical spec: [what is being built]
Status: draft | in review | approved | shipped | abandoned
Author:
Reviewers:
Last true on: [date the described plan last matched reality]
## Problem
What is broken or missing, and how you know. One paragraph. Include the number
that made this worth doing.
## Proposed approach
What you are going to build, in enough detail that a reviewer can say
"that will not work because...". Name the services, the data, the interfaces.
## What changes
The concrete surface area. New or modified tables, endpoints, queues, jobs,
config. This is the implementer's section.
## Rollout and rollback
Order of operations. What is behind a flag. The exact rollback, written so
somebody can run it at 3 a.m. without you.
## Alternatives rejected
One line each: what it was, and the specific reason it lost.
## Risks and what would change our mind
Each risk paired with the observation that would prove it real.
## Assumptions with expiry
Claims this plan depends on that are true today and may not stay true.
Each one gets a trigger: "if X ships, revisit this."
## Open questions
Things not decided yet, each with an owner and a date.Three of those fields are unusual, and they are the ones that pay off later.
Last true on is a date separate from "last edited". Editing a typo does not make a plan current. This is the same distinction that makes internal wikis untrustworthy: last edited is not last verified.
Assumptions with expiry turns a silent dependency into a tripwire. "We assume the vendor's rate limit stays at 1,000 requests per minute; if they announce a change, this design needs revisiting" is a sentence that can be checked later. "We assume reasonable vendor limits" is not.
Risks paired with observations stops the risk section from being decorative. Every real risk has a thing you would see if it were happening. Write that thing. Naming the unknowns this way is also how you narrow the range on your estimate: each risk you resolve on paper is a slice of the schedule you no longer have to guess at.
A worked example, abbreviated
Here is the middle of a real-shaped spec, compressed. The point is the texture, not the completeness.
## Technical spec: per-customer rate limiting on the public API
Status: in review
Last true on: 2026-08-10
## Problem
Three customers generated 61% of API traffic last month. One of them ran a
backfill on 14 July that pushed p99 latency from 180 ms to 4.2 s for everyone
else for 40 minutes. We have no per-customer ceiling.
## Proposed approach
Token bucket per API key, held in Redis, refilled at the plan's rate. Limits
are checked in the existing auth middleware, before routing, so a rejected
request never touches a handler. Over-limit requests get 429 with a
Retry-After header. Buckets are sized per plan, not per endpoint.
## Alternatives rejected
- Nginx rate limiting: dropped, limits are per instance and we run 12, so the
effective limit is 12x the intended one.
- Per-endpoint limits: dropped, a customer hitting one expensive endpoint is
the actual failure and per-endpoint budgets hide it.
- Queue and defer instead of rejecting: dropped, clients time out at 30 s and
a deferred request they no longer wait for is worse than a fast 429.
## Risks and what would change our mind
- Redis becomes a hard dependency of every request. If Redis p99 exceeds 5 ms
we will see it as added latency on all traffic, not just limited traffic.
- Buckets are per key, so a customer with six keys gets six buckets. If
support tickets about "the limit is wrong" start arriving, this is why.
## Assumptions with expiry
- Plan tiers stay at three. If usage-based pricing ships, bucket sizing moves
out of the code and this section is wrong.Read the alternatives section again. In a year, that is the only part anybody will need.
Technical spec vs design doc, ADR, PRD, and RFC
These overlap, and teams use the words loosely. What follows is the distinction that is worth keeping, which is about scope and lifespan rather than format.
| Document | Answers | Written | Lifespan |
|---|---|---|---|
| Technical spec | How will we build this specific thing | Before implementation | Until the code ships |
| Design doc | What should this system look like | Before or alongside the spec, usually broader | Until the design is superseded |
| ADR | Why did we choose this, and what did we reject | At the moment of decision | Forever, superseded but never deleted |
| PRD | What should the product do, and for whom | Before engineering starts | Until the feature ships |
| RFC | Should we do this at all, across teams | When the blast radius is wide | Until accepted or withdrawn |
| Runbook | What do I do when it breaks at 3 a.m. | After it ships | As long as the system runs |
The practical rule: if the document answers "why", it should end up as an ADR. If it answers "what do I type when this breaks", it should end up as a runbook. The spec is the thing in between, and it is the only one on that list with a short life by design.
Malte Ubl's account of design docs at Google makes the same point from a different angle: the value is concentrated in the writing and reviewing, not in the artifact left behind. The Stack Overflow engineering blog's guide to technical specs is the other piece worth reading, mostly for its treatment of getting reviewers to actually respond.
What to cut
Every guide adds sections. Here are the ones to remove.
- The glossary, unless the spec introduces a term that does not exist anywhere else. Otherwise it is four paragraphs nobody reads to define "idempotent".
- The architecture diagram of the existing system. It is already out of date and it is not what you are changing. If you do keep a diagram, write it as code so it lives in the diff rather than drifting as a stale image.
- Estimates in hours. They are wrong, they age instantly, and they turn a technical review into a scheduling argument. If you must put a number on it, at least know why that number always runs long before you defend it.
- Anything restating the ticket. Link the ticket.
- The success metrics section, if it is aspirational. Either name the number you will look at and when, or cut it.
- The full API schema. Put the two endpoints that change in the spec and link the schema.
- The complete future-state architecture. Specify the version you will actually build next, not the complex system you hope it becomes. The reliable path is to start with a simple system that works and grow it one validated layer at a time, beginning with the thinnest slice that runs end to end, so a spec that designs the whole end state up front is describing a system that does not exist yet and probably never will as drawn.
A ten page spec gets skimmed. A two page spec gets argued with. Only one of those produces the thing you wrote it for.
What happens to the spec after the code ships
The spec has done its job the moment the change is merged and reviewed. This is where teams go wrong in one of two directions.
Some delete it, and lose the rejected alternatives, which is the part with a long half-life. Others keep it in the wiki forever, where it sits at the top of search results describing a plan rather than a system, and gets read as though it were documentation. The second failure is worse, because it looks like an asset.
The handover is short, and it is worth doing on the day the pull request merges:
- Decisions with a long life become ADRs.
- Operational steps become a runbook.
- Usage becomes a README or an API doc.
- The spec gets marked
shipped, keeps its rejected alternatives, and stops being presented as current.
That last step is the one that never happens, because nothing prompts anyone to do it. The document doesn't know it went out of date, and neither does the wiki. This is the general shape of information half-life in engineering: nothing announces its own decay, so it accumulates silently until somebody trusts the wrong page.
Scribelet exists for that gap. Background checks re-examine the claims in a note against current sources and your own repository, and show you a diff of what no longer holds rather than a vague warning that something might be stale. For specs, the useful version is pointing it at your code: verify notes against your repo catches the case where the spec says one queue and the service has three, which is the exact drift that makes an old spec dangerous.
None of this writes the spec for you. It answers the question that comes after: of the documents you kept, which ones are still true. Try AI verification free and start with the oldest spec anybody still links to.
The short version
A technical spec is a plan for something that does not exist yet, written to be argued with before it is expensive to change.
Write it for three readers and keep them separate. The reviewer gets claims specific enough to be wrong. The implementer gets the boring, exact parts. The inheritor gets the alternatives you rejected and the reason each one lost.
Keep it to two pages. Give it a "last true on" date, assumptions with triggers, and risks paired with what you would observe. Then, at merge, hand the durable parts off to an ADR and a runbook and mark the spec shipped.
The document that describes your system is never the one you wrote before you built it.
Share this article