Learn when to write a Design Doc, what to include, and how to review architecture, trade-offs, rollout and metrics before expensive decisions turn into code.


Monday, 10 a.m.
The team is discussing how to prepare the platform for the new mobile application. The current API was built for the web, returns payloads larger than the app needs, and forces the frontend to make several calls to assemble the home screen.
In twenty minutes, three solutions appear.
One person wants to adapt the existing API. Another proposes creating a . A third suggests putting GraphQL in front of everything.
The meeting ends with a familiar sentence:
Let's start with the BFF and adjust along the way.
Two sprints later, the BFF already knows business rules, the cache has no clear invalidation strategy, Security finds out that tokens are being propagated incorrectly, and nobody remembers why GraphQL was dropped.
The problem was not a lack of code.
The problem was committing to a technical direction before making the proposal readable to the people who needed to evaluate it.
A Design Doc exists to bring that conversation forward: to turn a technical direction into something other people can question, improve or reject before it gains code, contracts and inertia.
It does not need to predict every class, method or implementation detail.
It should not become a fifty-field form either. Its job is to make clear the problem, the possible paths, the proposed direction and the evidence that will show whether it worked.
If you already use ADRs, there is overlap: an ADR can also start as a proposal and be accepted or rejected. The most useful difference is the focus. A Design Doc usually evaluates a whole solution; an ADR concentrates one specific architectural decision and its consequences.
For a smaller decision, an ADR may be enough. When the proposal involves several components, contracts, risks, metrics and rollout stages, the Design Doc organizes the whole and can give rise to several ADRs. Further down they appear side by side, next to RFC and Pull Request.
TL;DR
A Design Doc is a technical proposal written before or at the start of implementation to make a solution reviewable while there is still cheap room to change direction.
Use it when there are plausible alternatives, impact across modules or teams, operational risk, long-lived contracts, or decisions that are expensive to reverse.
A good document presents context, goals, non-goals, a high-level solution, alternatives, trade-offs, cross-cutting concerns and validation criteria. The template guides. The problem decides the size.
A Design Doc is a relatively informal document that describes how a technical problem will be tackled and why that direction makes sense.
The important word is proposal.
While the document is under discussion, the architecture can still change. The author is not merely announcing what they intend to build.
They are exposing assumptions and decisions so that other people find problems before the solution materializes into databases, contracts and infrastructure and goes into use.
After that, users and systems start depending on it, and changing direction already requires migration, coordination and more risk.
One way to see its role is:
The model above is not just a didactic way to summarize the role of the document. One of the best known descriptions of the practice comes from Google, where Design Docs are written before implementation and focus on high-level strategy, on the main decisions and on their trade-offs. The practice also helps to find problems early, gather distributed knowledge, consider cross-cutting concerns and preserve organizational memory.
That does not mean a Design Doc is a complete description of the system.
The document has to keep what the code does not preserve well:
The code will show the final implementation.
On its own, it will not preserve that the team dropped GraphQL because it needed to launch in eight weeks, that nobody had operational experience with the technology, or that the priority was reducing mobile traffic without creating a new API platform.
It is easy to confuse documentation written before the code with .
The difference is in what each one claims.
Big Upfront Design tries to eliminate uncertainty through early detail. A Design Doc tries to make uncertainty visible so the team can decide better.
A healthy document can say:
We do not know yet whether the cache will be necessary.
We will validate payload and latency without cache first. The decision will be reopened if p95 exceeds 350 ms.
That is more useful than inventing a complete cache strategy just to make the document look final.
The Design Doc guides the implementation, but it does not pretend that contact with code, load and production will stop teaching new things.
An idea can look perfectly clear while it is still in the head of the person who formed it.
When it goes onto paper, the gaps show up:
Writing forces the reasoning to stop relying on intuition.
The final result matters, but the process of shaping the document already produces value. It turns an individual conviction into a proposal other people can examine.
The size of the task is not the best ruler.
A mechanical migration that changes five hundred files can have an obvious direction and low architectural risk. A five-line change in how the system determines the current tenant, on the other hand, can shift an entire security boundary.
The question is not "is this task big?".
The question is:
Is there a relevant decision that would be better discussed before it gains inertia in the code?
Five signals help to answer it.
| Signal | Question | Example |
|---|---|---|
| Ambiguity | Are there two or more genuinely plausible solutions? | Adapt the current API, create a BFF or adopt GraphQL |
| Blast radius | How many modules, consumers or teams may feel the change? | A change to an API used by web, mobile and partners |
| Reversibility | How much does it cost to back out after the deploy? | Data migration or protocol change |
| Coordination | Who needs to agree or to provide knowledge? | Security, platform, data, SRE and consumer teams |
| Longevity | For how long will the system carry this decision? | Main database, authorization model or public contract |
A change tends to justify a Design Doc when several of these signals show up together.
A single signal can also be enough when it is critical, such as:
Practical heuristic
If the implementation is predictable, local and easily reversible, a good issue or Pull Request is probably enough.
If reasonable people can choose different paths and each path charges a relevant price, the Design Doc starts to pay for itself.
Design Docs also cost time.
It is not worth writing a full document for:
A bad sign is the document that turns into an implementation manual:
- Create class MobileCatalogController
- Create interface CatalogService
- Add method getCatalog()
- Write unit test
That can be a task, a checklist or a Pull Request description.
If the document only narrates the future code, without discussing alternatives, risks or choices, it would probably be better to write the code.
Not every doubt is settled with text.
Some questions only have an answer through measurement, and that is what the exists for:
In that case, the Design Doc does not compete with the prototype. It asks the question, points to the experiment and uses the result as evidence.
In the document, this shows up at both ends:
The same question, at the entrance and at the exit
Open questions. Should the Recommendations timeout be 100 or 150 ms?
References. Parallel composition spike: at 150 ms, 4% of the responses lose the recommendations block; at 100 ms, 11%.
"We tested it and it worked under these conditions" is a better argument than "we believe it will work".
One rule is still missing, and it is the one that separates a spike from simply starting to build: the experiment has a deadline and its code is disposable. Without both, nobody ever decides that the prototype became the solution. It just never gets deleted, and one day it is already in production. That is the scene that opened this article, under another name.
There is no universal template.
Even so, good documents tend to answer a similar set of questions.
| Part | Question answered | Common mistake |
|---|---|---|
| Header and overview | What is this proposal, who answers for it and what is its status? | Making the reader find that out in the middle of the text |
| Context and scope | Which problem exists and which limits matter? | Starting by selling the solution |
| Goals and non-goals | What has to improve and what will not be solved now? | Using vague goals or excluding basic obligations |
| Proposed design | How will the solution work at a high level? | Jumping from a generic diagram to classes and methods |
| Alternatives | Which other paths were plausible? | Creating façade options |
| Trade-offs | Which price does each option charge? | Recording benefits only |
| Cross-cutting concerns | What changes in security, operation, data and reliability? | Treating production as a later detail |
| Rollout and validation | How do we launch, measure and decide to continue or go back? | Taking approval as proof of success |
| Open questions | What still needs to be discovered or decided? | Hiding uncertainty to look finished |
These parts do not have to become nine chapters in every document.
A Mini Design Doc can solve several of them in a few paragraphs.
The template should bring important questions to mind. It should not force the author to invent content just to fill fields.
Compare the two possible openings of the same document:
Context that decides for the team
We need to create a BFF to improve the performance of the application.
Context that lets the team decide
The home screen of the application makes five requests, transfers 420 KB on the median load and duplicates on the client composition rules that already exist in the backend. On higher latency mobile networks, this delays the first render and increases the drop-off rate for the journey.
In the first one, the solution arrived before the problem was demonstrated.
Notice what changed: instead of discussing how to implement a BFF, the team can now decide which path solves the problem better.
Maybe the BFF is the best direction.
Maybe the existing API can provide a mobile representation.
Maybe the problem is in the images, and a new backend layer solves almost nothing.
The context does not have to be so neutral that it hides urgency. It just should not use the solution as a premise.
"Improve performance" is not a usable goal.
It does not say which aspect of performance matters, where we are starting from, or how much it needs to improve.
A mature goal works like a small validation contract.
The example below is fictional:
## Goal
Reduce the bytes transferred on the home screen of the application.
**Baseline:** p50 = 420 KB per load over the last 14 days.
**Target:** p50 <= 290 KB after the full rollout.
**Outcome metric:** mobile.home.payload_bytes
**Adoption metric:** at least 95% of mobile sessions served by the BFF.
## Guardrails
- Error rate <= baseline + 0.1 percentage point.
- Backend p95 latency <= baseline + 5%.
- Monthly cost within the approved budget.
## Decision criteria
- Pause the rollout if a guardrail is violated.
- Reassess the design if the target is not reached after 7 days.That block separates three questions.
Reducing payload and doubling the error rate is not success.
It is also no good to improve the experience of 10% of the sessions and declare victory over the whole system.
Mature processes, such as the Kubernetes Enhancement Proposal template, bring proposal and production closer together by requiring test plans, a graduation and rollback strategy, metrics and readiness criteria. A product Design Doc does not need to copy the Kubernetes process, but it can learn from the principle: the technical decision does not end when the design looks convincing.
A non-goal is something related to the problem that could be in scope but was deliberately left out.
For example:
Non-goal: replace the API used by the web frontend.
That keeps the BFF discussion from turning into a rewrite of the whole platform.
But there is a trap. Minimum security is not an optional feature that can be excluded from the design:
Non-goal that misleads
Security of the BFF. It removes from the design an obligation the solution will have to meet anyway in order to reach production.
Non-goal that limits
Introducing a new authentication mechanism. The BFF will reuse the current mechanism and must preserve the existing controls for authentication, authorization and data protection.
The non-goal limits the space of the change.
It does not remove the requirements the solution needs in order to reach production.
The design section starts with the overview.
It should let someone understand:
Only after that come the details needed to evaluate the proposal.
One question resolves most of the doubt about the level of detail:
Does this detail change the evaluation of the solution?
If it does, it belongs in the Design Doc.
If it only describes mechanical work, it probably belongs in the code, in the formal API, in the migration or in the task.
Some examples:
Diagrams also need a purpose.
A context diagram can show where the new component fits. A sequence diagram can reveal calls, dependencies and failure points. Neither of them is self-explanatory: after the drawing, describe the flow and highlight what needs review.
A weak proposal makes the preferred option look excellent and turns the others into caricatures.
A more honest comparison lets each alternative win on some axis.
| Alternative | Where it is strongest | Main price | Outcome |
|---|---|---|---|
| Adapt the current API | Lower operational cost and faster delivery | Increases coupling between web and mobile needs | Rejected |
| Composition in the application | Creates no new service | More calls, duplicated logic and greater dependency on the network | Rejected |
| Mobile BFF | Specific contract and control over composition | New service, deploy, observability and ownership | Recommended |
| GraphQL gateway | Flexibility for multiple clients | Operational learning curve and a change larger than the current problem | Reassess later |
GraphQL does not have to be technically inferior to lose.
It can be the most flexible option and still not fit the deadline, the operational capacity or the current size of the problem.
That is a real trade-off.
The chosen option does not win everywhere. It serves the goals better within the available constraints.
Some concerns cut across any solution and usually show up too late:
There is no need to write a treatise on each item.
The document has to say how the proposal affects each relevant concern and to point to separate analyses when the topic requires depth of its own.
For example:
Observability. The BFF will propagate trace context to the three dependencies. We will have metrics per dependency, timeout and fallback.
Degradation. A failure in recommendations will not take the screen down. The block will be omitted and the response will still carry catalog and pricing.
Rollback. The application will remain able to use the previous composition during the initial rollout.
That alone already changes the quality of the review quite a lot.
A Design Doc that ends at Approved is incomplete.
Approval shows that the direction looks reasonable with the information available. It does not prove that the implementation respected the design, nor that the result showed up in production.
A lightweight flow can follow five steps.
Write the first draft. The author organizes context, goals, options and proposal. Co-authors can take part when the knowledge is distributed.
Review with people who know the problem. Before sending it to a large audience, involve people close to the domain. They correct basic assumptions without consuming the attention of the whole organization.
Ask for targeted review. Security reviews the trust boundary. SRE looks at operation and failures. Consumer teams check contracts. Each person needs to know which part requires their attention.
Record the decision and implement. The proposal can be approved, rejected, deferred or sent back for revision. During implementation, the document tracks discoveries that change the design.
Validate and preserve the history. After the rollout, compare metrics and guardrails. When the solution stabilizes, preserve the rationale and connect future changes to new documents or amendments.
Mixing the roles creates two problems: nobody knows who decides, and every comment seems to carry veto power.
A practical split:
Not every Design Doc needs to formalize all of these roles.
The larger the blast radius, the more important it becomes to make authority explicit.
| Reach of the change | Expected review | Decision |
|---|---|---|
| Local and reversible | Peers on the team itself | Technical owner of the team |
| Contract between teams | Producers and consumers | The owners involved |
| Security, data or operation | The specialists responsible | Owner plus mandatory sign-off |
| Platform or organization | Representatives of the affected groups | Cross-cutting forum or owner |
The mistake lives at the extremes.
With no review, the document becomes a written justification produced after someone already decided alone.
With central approval for everything, the Design Doc joins a queue and the team learns to work around the process.
The review does not need to gather twenty people.
It needs to include the people who can find risks the author cannot see.
"Take a look whenever you have some time" is not a review process.
The document has to state:
There is no universal deadline.
A local decision can collect comments in a few days. A change to a public API or to shared infrastructure may require a longer window.
The Rust RFC process shows a formal version of this idea: the proposal gets public discussion, the author revises it in response to the feedback, and the decision only happens after a final comment period of ten calendar days, announced publicly. A product squad does not need to copy that mechanism, but it should copy the principle that comments have an explicit moment, an explicit responsibility and an explicit closing.
Silence is not approval by accident
A team can adopt lazy consensus deliberately.
What does not work is leaving the proposal parked, assuming that nobody complained and calling that alignment.
Define the rule beforehand.
During implementation, the plan meets reality.
An API does not support the expected behavior. The database picks another plan. A requirement shows up late. The cost of the solution changes.
If the system has not launched yet and the change alters the design, update the document.
Once the solution reaches production and stabilizes, the role starts to change.
The Design Doc stops being only a live proposal and becomes a historical record as well. Silently rewriting the original rationale can erase the very context the document was supposed to preserve.
A simple policy:
The example below is fictional.
It does not try to be a universal model. It shows how much context fits into a short document when each section has a clear function.
# DD-0042: Create a BFF for the mobile home screen
* Status: Under Review
* Owner: Mobile Platform team
* Authors: Ana Souza, Bruno Lima
* Required reviewers: Catalog, Security, SRE
* Decision deadline: 2026-09-04
* Systems impacted: Mobile App, Catalog, Pricing, Recommendations
## Overview
We propose creating a BFF to compose the data of the home screen of the
application into a single response oriented to mobile needs.
The component will aggregate catalog, pricing and recommendations, without
taking on domain rules that belong to those services.
## Context and scope
The home screen makes five requests and transfers 420 KB on the median
load.
The application replicates composition and fallback logic that already
exists in other clients. On higher latency networks, the number of round
trips delays the first render.
The current API also serves the web frontend. Changing it to optimize
mobile only increases the coupling between different experiences.
## Goals
* Reduce the p50 payload from 420 KB to at most 290 KB.
* Reduce the number of calls made by the application from five to one.
* Preserve the current error rate.
* Keep backend p95 latency within 5% of the baseline.
* Allow partial degradation when Recommendations is unavailable.
## Non-goals
* Replace the API used by the web frontend.
* Introduce a new authentication mechanism.
* Move catalog, pricing or recommendation rules into the BFF.
* Create a corporate GraphQL platform.
## Proposed design
The application will call GET /mobile/v1/home.
The BFF will query Catalog, Pricing and Recommendations in parallel.
Catalog and Pricing are required. If one of them fails, the request
fails.
Recommendations are optional. On timeout or error, the block will be
omitted.
The BFF will have no database of its own. A 30 second cache may be used
only for non personalized content, after we measure the solution
without cache.
Flow:
Mobile App
|
v
Mobile Home BFF
|----> Catalog
|----> Pricing
|----> Recommendations
## Initial contract
GET /mobile/v1/home?storeId={storeId}
Response:
* hero
* categories[]
* featuredProducts[]
* recommendations[]
* generatedAt
The absence of recommendations does not make the whole response an error.
## Alternatives considered
### Adapt the current API
It is the fastest option and does not create another service.
It was rejected because it mixes web and mobile needs in the same
contract and keeps composition distributed across clients.
### Keep composition in the application
It avoids a new backend component.
It was rejected because it keeps five round trips, duplicates fallbacks
and pushes knowledge about three internal services to the client.
### GraphQL gateway
It is the most flexible option for multiple clients.
It was not chosen now because it introduces a larger change in platform,
schema governance and operation. It should be reassessed if other clients
show the same need.
### Mobile BFF
It adds a service and operational responsibility.
It was chosen because it concentrates mobile specific composition without
changing the domain contracts and without starting a broader platform.
## Security and privacy
The BFF will reuse the current authentication mechanism.
Authorization will still be applied by the domain services.
The BFF will not trust filters sent by the application alone.
Tokens will not be written to logs.
## Observability and operation
* Distributed tracing between BFF and dependencies.
* Latency, error, timeout and fallback metrics per dependency.
* Dashboard for payload and mobile adoption.
* Operational owner: Mobile Platform team.
* Runbook for total failure and partial degradation.
## Rollout and rollback
The application will temporarily keep the previous flow behind a
feature flag.
Rollout: 5% -> 25% -> 50% -> 100%.
Moving forward depends on the error and latency guardrails.
If one is violated, traffic returns to the previous flow with no new
deploy.
## Validation
Outcome:
* mobile.home.payload_bytes p50 <= 290 KB.
Adoption:
* at least 95% of mobile sessions using the BFF.
Guardrails:
* error rate <= baseline + 0.1 percentage point;
* backend p95 <= baseline + 5%;
* monthly cost within the approved budget.
## Open questions
* Should the Recommendations timeout be 100 or 150 ms?
* Do we need cache after the initial rollout?
* Does the generatedAt field belong to the response or to telemetry?
## References
* PRD of the mobile application
* Home screen dashboard
* Parallel composition spike
* ADR on service to service authenticationNotice what the example does not do.
It does not define class names.
It does not paste the whole OpenAPI.
It does not describe every field of the services it queries.
It also does not try to solve GraphQL for the entire company.
The document preserves the decisions that change the evaluation of the proposal: the boundary of the BFF, required and optional dependencies, ownership, fallback, alternatives, rollout and evidence.
To show the contract without sinking into detail, a short representation could look like this:
/mobile/v1/home?storeId=rec-01{
"hero": {
"title": "Deals of the week"
},
"categories": [],
"featuredProducts": [],
"recommendations": [],
"generatedAt": "2026-08-30T18:20:00Z"
}The rest belongs to the formal contract and to the code.
That is the level of decision detail: enough to review the direction, short enough to stay readable.
These names have no universal boundaries.
One company calls this kind of document a Design Doc. Another uses RFC, Engineering Review Doc, Technical Proposal or Tech Spec.
The function matters more than the label.
| Artifact | Main question | Typical moment | Level of detail |
|---|---|---|---|
| Issue or PR | Which specific change will we make? | Implementation close or underway | Code, tasks and local impact |
| Design Doc | How should we solve this problem, and why? | Before or at the start of implementation | Solution, alternatives, risks and rollout |
| ADR | Which architectural decision are we evaluating or preserving, and why? | From the proposal to the decision record | One decision, alternatives and consequences |
| RFC | Which proposal needs broader comment and alignment? | Before standardization or a cross-cutting change | Varies with the organization |
A Design Doc can give rise to an ADR.
The broader document keeps the problem, the flows, the experiments and the deployment strategy. The ADR concentrates one specific architectural decision, and it can start as a proposal and remain afterwards as a historical record.
For example:
An RFC can play exactly the role this article assigns to the Design Doc.
There is no problem with that.
The mistake is keeping four nearly identical formats without anyone knowing when to use each one.
A practical rule:
Use the lowest cost artifact that preserves enough context for the decision at hand.
If a good Pull Request description solves it, stop there.
If there is a whole solution to review, use a Design Doc.
If a specific architectural decision needs to be discussed and then preserved, use an ADR.
If the proposal requires broad comment or organizational standardization, your process may call it an RFC.
Adoption does not start by buying a tool or creating an architecture board.
It starts with the next ambiguous decision.
Choose a minimal document. Use overview, context, goals, non-goals, design, alternatives, risks and validation. Remove whatever brings no information.
Define when it applies. Use ambiguity, impact, reversibility, coordination and longevity. Do not use the number of files or the size of the estimate.
Choose one official source. Git, a wiki or a collaborative editor can all work. The problem starts when there are three competing copies.
Define who reviews and who decides. Required reviewer, approver and deadline need to be clear when the impact justifies it.
Connect document and implementation. The Design Doc points to issues and Pull Requests. The implementation points back to the document.
Validate after the launch.
Compare target, adoption and guardrails. Record the result instead of abandoning the document in the Approved state.
Adjust the process with use. A field nobody fills in goes away. A review that finds no risk gets redirected. A document nobody ever consults calls for investigation.
For teams that naturally work in Git, a simple structure can be:
The README.md can work as an index, showing status, owner, domain and impacted systems.
GitHub lets you use CODEOWNERS to automatically request reviews from the people or teams responsible for certain paths and, when branch protection is configured for it, to require approval from those owners.
That can serve highly technical documents:
/docs/design/security/ @security-team
/docs/design/payments/ @payments-architecture
/docs/design/platform/ @platform-teamBut Git is not automatically the best choice for every organization.
If product, legal, design or operations take part regularly, a collaborative editor or wiki can reduce friction.
In larger companies, a hybrid model usually works better:
The tool should make writing, review, discovery and maintenance easier.
Everything else is preference.
The sign of success is not a full folder.
It is finding problems early, making clearer decisions, reducing repeated discussions and being able to explain why a solution took a certain shape.
Before we close, five situations to judge: when the document pays for itself, what a non-goal can exclude, what counts as success after the rollout and what needs to become an ADR.
Four changes land in the same sprint. Which one most looks like a Design Doc?
The first version of the BFF could have started with code. One endpoint, three HTTP clients, a DTO and the promise of fixing the rest later.
That looks fast until the team finds out, during implementation, that each person was solving a different problem.
The Design Doc puts that conversation in the right place.
It does not eliminate uncertainty. It keeps uncertainty from staying hidden until production.
That is the central point:
A Design Doc does not exist to predict all the code. It exists to make a technical decision reviewable before changing your mind gets expensive.
There is probably an open decision in your team right now, one of those that has already come up in two meetings and keeps receiving different solutions.
Before choosing the technology, write five things:
If no trade-off shows up, maybe a good Pull Request is enough.
If the decision gets clearer before the first line reaches production, the Design Doc has already paid for itself.
A practical writing guide, plus three open processes you can read from the inside to see how proposals, review windows and ownership work in practice.
Software Architect and Engineer | Java & AI