Raw capture â raw/how-to-write-an-effective-design-doc.md
url: https://refactoringenglish.com/excerpts/write-an-effective-design-doc/ date_fetched: 2026-07-05 backfilled: true
A good design doc can save you years of development time. Writing a design doc forces you to think through important decisions before you waste time on the wrong implementation or paint yourself into a corner. Itâs also the best way to coordinate design decisions among teammates and partner teams.
Iâve written design docs as a developer at Google, Microsoft, and within my own companies. The specifics vary, but the underlying principles remain the same. A design doc should articulate the hard problems youâre solving and help your teammates give you feedback.
Below, I share my approach to creating effective design docs and explain what belongs in a design doc and what does not.
The most common question I get about design docs is where to find a good one. Iâve never seen a public design doc that I consider high-quality. All of mine are hidden away at the companies that paid me to write them.
So, I wrote a design doc from scratch based on the principles Iâm sharing here. It lays out the design for a real web app Iâm building.
I created the design doc before writing any code, and Iâm adhering to the design as I implement the app.
The design is more exhaustive than what Iâd normally write for a solo hobby project, but this is roughly the length and depth of a design doc Iâd create if I were coordinating work with other people on a professional project.
The more complex or risky the project, the more valuable it is to write a design doc.
Consider these questions:
If you answered âyesâ to any of these questions, then itâs likely worth the effort to write a design doc. If you answered âyesâ to two or more, a design doc will almost certainly be worth the effort.
A design doc can be a simple one-pager or a 50-page document that requires signoff from five different teams. You need to decide how much detail makes sense.
Thereâs no universal rule that says how long you should spend on a design doc just like thereâs no rule that says how much to test your code. The right investment depends on your teamâs goals, risks, deadlines, and culture. Sometimes, the right amount to invest in a design doc is zero.
If you specify every possible detail in a design doc, youâve essentially written the implementation during the design phase. That would defeat the whole purpose of a design doc.
As a rule of thumb, you can ask a simple question to decide whether a decision belongs in your design doc: whatâs the penalty for being wrong?
Not all design decisions are equally important. Some choices are radically more flexible than others.
For example, if you build a web application in C++ and realize 200k lines later that Ruby on Rails was the better choice, youâre stuck. A from-scratch rewrite would never work, and even if you manage to write new code in Rails, you still suffer the burden of maintaining code in two wildly different languages.
Other design decisions are trivial. For example, if your app displays a list of 1,000 articles, should they all appear at once? Or should the user see 20 at a time and click âLoad moreâ to see the next 20?
It doesnât matter.
A âload moreâ button is not a design-level concern. If you pick one solution, and user feedback tells you youâre wrong, you can fix it in a few hours. You donât need to detail your entire thought process in your design doc, and you definitely shouldnât waste review cycles arguing about it.
Below, Iâve included common sections to include in your design docs. You generally donât need every single section for every doc. Choose the subset that make sense for you.
The first thing your project needs is a title. Itâs the way people will refer to your project in conversation, so aim for something with these qualities:
For example, if you were adding a caching layer between your application server and your database server, RecencyBank would be a good name. Itâs easy to say and describes your projectâs purpose. A bad name would be âProject Flying Silver Horseâ because itâs verbose and nonsensical.
Boring but useful, metadata helps your reader understand the basic context of your doc:
Whatâs the authoritative URL?- Especially if your organization uses shortlink redirects like http://go/recency-bank
Especially if your organization uses shortlink redirects like
Metadata
Author: Michael Lynch (michael@refactoringenglish.com) Status: Ready for review Created: 2026-06-22 URL: http://go/recency-bank-design
The objective is a one-sentence explanation of your projectâs purpose. It should appear on the first page of your doc in plain language that any stakeholder understands.
ObjectiveImprove application performance by adding a caching layer between the Trogdor web server and the Postgres database.
The background section explains the context and motivation for the project. It should answer these questions:
BackgroundWhen we launched the Trogdor web app in 2023, pages typically loaded in 100ms or less. After three years, median page loads have ballooned to 600ms, which causes users to perceive our app as sluggish.
We investigated the slowdown and discovered that database lookups make up 80% of page load times. As our data store has grown larger, database lookups have gotten slower.
We also discovered that 95% of database lookups are for the same 3% of database rows. This pattern of usage benefits greatly from memory-backed caching. The cache would serve frequently-accessed data faster and reduce database load for all other queries.
Does your design doc make sense without outside context?
Imagine what youâd say to a teammate or partner team before they read your design doc.
Now, realize that some readers will see the doc before hearing any explanation from you, so whatever they need to understand should be on the first page of your doc.
If this project connects to other documents, link to them so itâs easy for the reader to find them. This includes:
Related documents
Testing plan: http://go/recency-bank-test-plan Trogdor performance report: http://go/trogdor-perf-2026
The goals section describes your high-level goals for this project. It should connect logically to the background section and explain what the world looks like after youâve completed implementation.
Avoid setting goals in terms of implementation details. Your goals should communicate how the project benefits your users, your team, or your company.
Add Kubernetes to our infrastructure.
Minimize outages related to deploying new app versions.
Goals
While the goals define whatâs within your projectâs scope, the non-goals section delineates whatâs out of scope.
Are there goals that readers might mistakenly assume are within scope for your project? If so, add them as explicit non-goals.
Non-goals
Create a general-purpose, reusable caching system
The caching layer we add to the Trogdor web app will make application-specific optimizations. Re-using this cache on other systems is out of scope.
Location-aware caching
It may be useful in the future to support caches that sit geographically close to the end user to reduce latency, but that is out of scope of v1.
If your goal is something like âAdd a âShare as URLâ button to charts,â the reader might not understand what that looks like in practice.
The scenarios section allows you to paint a picture for your reader of how your completed system works in the real world.
Scenario: Share a report via URL
Diagrams are tremendously valuable, though they might not seem that way.
As the design author, you intuitively understand how the pieces of your plan fit together. You can see the architecture in your head. Your reviewers do not have this mental picture, so the fastest way for them to see it is to draw them a picture.
If youâre not sure what belongs in a diagram, think about these questions:
Choose a diagramming tool thatâs flexible to editing. Iâve seen developers create a beautiful diagram on a whiteboard and photograph it for their design doc. The first draft looks amazing, but then theyâre stuck with that diagram forever because they canât edit the photo without recreating the whole thing from scratch.
Excalidraw, draw.io, and Google Drawings are popular diagramming tools that facilitate revisions. There are also languages like Mermaid, D2, and Graphviz that allow you to generate diagrams programmatically. Iâve had good experience using an LLM to create diagramming code for me. Remember to link to the source drawing or code so that your teammates have a way to reproduce the diagram as well.
The glossary defines terms that your readers might not recognize.
Think hard about the potential readers of your doc, especially newer team members and people outside of your immediate team. Will those readers understand the names of internal tools or systems your doc references?
When possible, use terms that your audience recognizes without having to refer to a glossary. Defining a term in a glossary is better than not defining it at all, but the best solution is to use recognizable terms or define them inline so that the reader doesnât have to jump around your document.
Glossary
Apposaurus: the teamâs internal load testing tool. We use Apposaurus to simulate a surge of visitors to the Trogdor web app so we can verify the app continues functioning under expected workloads. Baba-o-styley: an internal code linter that enforces the companyâs code style conventions.
If there are major constraints imposed on your design by your budget, clients, infrastructure, or dependencies, explain the constraints so the reader understands the context of your design choices.
ConstraintsOur servers are all RISC-V, so all code and dependencies must run on RISC-V architecture.
SLOs are the measurable goals that a service offers to its clients or users. Youâve probably heard of service level agreements (SLAs). SLAs are just SLOs plus financial penalties for falling short.
Within a company, you typically donât financially penalize your co-workers for mistakes (although, wouldnât that be kind of fun?). So, design docs define SLOs rather than SLAs.
An SLO creates a measurable, objective metric for your systemâs performance. Your manager might tell you that your app must be âperformant on mobile,â but thatâs vague. You donât want to wait until code complete to discover that your managerâs definition of âperformantâ is <2ms of latency. A well-defined SLO prevents ambiguity by expressing goals in concrete, objective terms.
The typical considerations for your SLO are:
Service level objectives
Once you nail down your SLOs (above), itâs time to think about how youâll measure them in production.
The simplest way to verify that youâve achieved your SLOs is to test manually. As your organization matures, you should automate monitoring to discover SLO failures immediately.
When defining your monitoring strategy, ask yourself these questions:
MonitoringThe following events will trigger a page to the on-call engineer:
The timeline section breaks your project into milestones and specifies when project stakeholders will receive their deliverables.
Choose milestones that create useful artifacts for stakeholders. For example, start with a UI that shows dummy data, and show that to clients first. If it turns out you misunderstood the clientâs requirements, fake data lets you find out early rather than after youâve already implemented all the plumbing to populate the UI with production data.
If you donât know how to estimate project timelines, I highly recommend Joel Spolskyâs, âPainless Software Schedules.â The article is 25 years old, but it remains my favorite software estimation strategy.
Timeline
Milestone 1 (2026-07-01): RecencyBank is live in the test environment with a hardcoded subset of cached data (doesnât read from Postgres). Milestone 2 (2026-07-17): RecencyBank is live in the test environment and caches real data from Postgres. Milestone 3 (2026-08-03): RecencyBank is live in the test environment and enforces cache eviction and lifecycle rules. Milestone 4 (2026-08-22): RecencyBank is fully implemented and deployed to production.
Your project exists to serve people or other software systems, so what do those interactions look like?
For graphical systems, what is the user interface?- Just simple sketches; donât get bogged down in precise UI choices.
For software interfaces, what are the API or CLI semantics?
InterfacesThe Trogdor
Serverstruct currently depends directly on aPostgresDBGostructlike this:type Server struct { db PostgresDB }
PostgresDBhas the following exported methods:GetUser(id UserID) (User, error) ListUsers() ([]User, error) ...We will create a Go
interfacetype with the same API surface asPostgresDB:type Store interface { GetUser(id UserID) (User, error) ListUsers() ([]User, error) }We will implement a RecencyBank caching type that implements the same
interfaceand wraps the backendPostgresDBstruct. The RecencyBank implementation will cache reads from Postgres and forward requests to Postgres when they mutate state or depend on data not in the cache.The only change to the
Serverimplementation will be replacing the type of one member with the newinterface:type Server struct { db store.Store }
The dependencies section should answer questions like:
Itâs easy to overlook this section, but decisions about language, libraries, and infrastructure have a major impact on the complexity and long-term maintenance costs of your system.
Think deeply about which dependencies will be difficult to change after implementation, and donât worry so much about the ones that swap out easily. Itâs difficult to change languages or storage backends, but if youâre dissatisfied with the third-party service you use to send emails, you can replace it in an afternoon.
Dependencies
Language: Go
We widely use Go already, and itâs a suitable language for serving highly parallel workflows. Third-party packages
bbolt: This is a widely used key-value store implementation that implements many of the features we need for RecencyBank.
To build secure software, developers must integrate security into the full software lifecycle, starting at the design stage.
The security section should answer questions like:
What threats did you consider?- e.g., what happens if an attacker tries every possible password? What if a user uploads a PDF infected with malware?
What is the attack surface of this system?- i.e., where does it process potentially malicious data?
What are the trust boundaries?- At what point does data flow from a less privileged system to a more privileged system?
Even if you think security threats are unlikely or irrelevant in your system, itâs still helpful to document your rationale. Your explanation might prompt reviewers to identify threats you overlooked.
SecurityRecencyBank must not accept direct requests from the public Internet, as it does not enforce any access control.
RecencyBank will run on a segregated network where it only accepts inbound requests from the Trogdor web server and can only make outbound requests to the Postgres server pool.
The privacy section is an opportunity to think through the sensitive data your system handles and what safeguards youâll put in place to keep it secure. It should answer these questions:
PrivacyRecencyBank contains the same sensitive user data as the Postgres database, so it inherits the privacy policy of our Postgres systems. In particular, engineers may only access RecencyBank systems in production with an associated bug number. Engineers must minimize the user data they access to only what is strictly required to investigate a bug.
If your system operates in a highly-regulated domain like finance or healthcare, the legal section helps you comply with relevant laws.
Even outside of regulated domains, think about whether your system could break the law if things go awry. Explain how youâll steer clear of legal violations that could put your company or clients at risk.
If youâre publishing your code under an open-source license, define which license youâve chosen and why.
FizzleCorp contractual complianceOur contract with FizzleCorp strictly limits our ability to create new copies of their proprietary FizzlePerfect⢠user biometrics.
Fortunately, our legal team reviewed the wording of the contract and confirmed that a caching layer fits within the existing definition of âstorage layer,â so we may cache FizzlePefect⢠data within RecencyBank without contract renegotiation.
Logs can be tremendously valuable when youâre investigating a bug, performance issue, or security incident. If you design for effective logging, youâll make it easier to maintain your system long-term.
As you think about logging, consider these questions:
Are there different log levels?- e.g., informational, warning, error, critical
Where does the system store its logs?
LoggingRecencyBank logs the following events:
As you write your design doc, youâll likely encounter at least one of the following situations:
Create an appendix in your design doc called âOpen Issuesâ that documents your outstanding issues.
Each entry in the open issues section should explain:
Open Issue: Choosing RAM size for cacheWe need to decide how much RAM to assign to our caching layer. Adding RAM increases performance, but RAM is expensive, and there are diminishing returns to extra RAM.
There is some optimal amount of RAM that minimizes our infrastructure costs between the caching system and our database. We could theoretically discover that optimal value by setting up a test environment and running several simulations, but running those simulations costs us dev time.
I estimate the cost of creating a test environment and running a single simulation to be 3.0 dev days. Once the infrastructure is in place, additional simulations will take about 0.75 dev days each.
Proposed solution: Choose 128 GB of RAM without testing. Itâs probably close to optimal, and dev time is significantly more expensive than RAM.
Next step: Ask our tech lead to weigh in.
When you resolve an open issue, summarize the decision, and move it from âOpen issuesâ to a âResolved issuesâ section in your design doc. Retain the full discussion for posterity.
Resolved Issue: Choosing RAM size for cache
Decision: Provision 128 GB of RAM to the caching layer. If weâre failing to meet our performance goals and weâre RAM-constrained, we can add more RAM at that point. The dev cost of running tests to discover the perfect RAM size far outweighs the cost of additional RAM.We need to decide⌠[rest of original open issue goes here]
If you anticipate readers asking, âWhy didnât you do X?â itâs helpful to answer that proactively in an âalternatives consideredâ section. This section is also where you can explain options you rejected, especially if they initially seemed appealing or you researched them extensively.
I know some developers who spend hours meticulously documenting their every rejected design idea, but I think thatâs overkill. As both a reader and author, all I need in the alternatives section is a few brief lines describing strong alternatives and why they didnât work.
Alternatives Considered
Google Cloud Firestore (persistent storage)
The durability and reliability was appealing, but I disliked the platform lock-in and the difficulty of testing locally.
Once youâve completed your design doc, the next step is to share it with your team and gather feedback.
The following section covers techniques for eliciting useful design feedback that moves your project forward rather than stalling it with bickering and confusion: