How to Write an Effective Software Design Document
How to Write an Effective Software Design Document
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. 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 articulates the hard problems youâre solving and helps 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.
- An example design doc
- When should you write a design doc?
- How much should you invest into your design doc?
- What belongs in a design doc?
- Components of a design doc
- Driving Your Design Doc through Review
An example design docđ
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.
When should you write a design doc?đ
The more complex or risky the project, the more valuable it is to write a design doc.
Consider these questions:
- Will multiple people coordinate work to implement the design?
- Will the project take more than three months of full-time dev work?
- Will the implementation run in production for several years?
- Does the project involve cross-team collaboration?
- Are the goals and requirements of the project ambiguous?
- Are there catastrophic risks you could prevent at design time (e.g., security flaws, legal risks)?
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.
How much should you invest into your design doc?đ
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.
What belongs in a design doc?đ
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?
Whatâs the cost of getting it wrong?đ
Not all design decisions are equally important. Some choices are more permanent 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âre still maintaining code in two wildly different languages.
Other design decisions are trivial. For example, if your app displays a list of 100 articles, should they all appear at once? Or should the user see 25 at a time and click âLoad moreâ to see the next 25?
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.
Components of a design docđ
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.
Titleđ
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 short, distinctive, and evocative.
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.
Metadatađ
Boring but useful, metadata helps your reader understand the basic context of your doc:
- Who is the author? (name + email address)
- When did you create the 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
- Who approved this document and when?
- In cases where your document requires signoff from teammates or partners.
Metadata
- URL: http://go/recency-bank-design
- Author: Michael Lynch (michael@refactoringenglish.com)
- Created: 2026-06-22
- Status: Approved
- alan@ signed off, 2026-07-14
- betty@ signed off, 2026-07-15
Objectiveđ
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.
Objective
Improve application performance by adding a caching layer between the Trogdor web server and the Postgres database.
Backgroundđ
The background section explains the context and motivation for the project. It should answer these questions:
- Why is the team taking on this project?
- What problem does this project solve?
- Were there previous attempts to solve this problem?
Background
When 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.
Related documentsđ
If this project connects to other documents, make it easy for the reader to find them.
Include links to:
- Documents from your program manager or testing counterparts on this project (e.g., test plans, functional specs)
- Design docs for related systems
- Design docs for previous iterations of this project
Related documents
- Testing plan: http://go/recency-bank-test-plan
- Trogdor performance report: http://go/trogdor-perf-2026
Goalsđ
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
- Increase user-perceived responsiveness for Trogdor web app.
- Reduce database server load.
Non-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.
Scenariosđ
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
- Bob creates a custom report in his KeyMetrics dashboard.
- Bob navigates to the menu bar and clicks âShare > as URL.â
- Bob emails the URL to his teammate, Charlie.
- Charlie clicks the link and sees an exact copy of Bobâs report in read-only mode.
Diagramsđ
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:
- How does data flow through your system?
- How do the different components of your system fit together?
- How does your system interact with its dependencies and downstream clients?
- What communication protocols does your system define?
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.
Glossaryđ
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.
Constraintsđ
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.
Constraints
Our servers are all RISC-V, so all code and dependencies must run on RISC-V architecture.
Service level objectives (SLOs)đ
An SLO creates a measurable, objective metric for your systemâs performance. 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.
Your manager might tell you that your app must be âperformant on mobile,â but thatâs vague. Your managerâs idea of âperformantâ might be <2ms of latency, and you donât want to wait until code complete to find out. A well-defined SLO prevents ambiguity by expressing goals in concrete, objective terms.
The typical considerations for your SLO are:
- Uptime / availability: What percentage of time will your system be available?
- Latency: How quickly will your service complete requests?
- Scale: What volume of work can your system handle?
Service level objectives
- Trogdorâs 50th percentile latency for user-facing HTTP requests: <=200ms
- Postgres 50th percentile query latency: <= 80ms
Monitoring / alertingđ
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:
- If your service goes down, how will you find out?
- If your serviceâs performance slows by 100x, how will you know?
- What other events should trigger an alert?
- e.g., spikes in CPU usage, authentication failures, system errors
Monitoring
The following events will trigger a page to the on-call engineer:
- Trogdorâs 95th percentile latency for user-facing HTTP requests: >= 3s
- Average CPU usage for Postgres servers during trailing 2m window: >= 90%
Timelineđ
The timeline section breaks your project into milestones. It specifies when youâll deliver results to project stakeholders.
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.
Interfacesđ
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?
- For file-based interfaces, what is the file format?
Interfaces
The Trogdor
Server
struct currently depends directly on aPostgresDB
Gostruct
like this:type Server struct { db PostgresDB }
PostgresDB
has the following exported methods:GetUser(id UserID) (User, error) ListUsers() ([]User, error) ...
We will create a Go
interface
type 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
interface
and wraps the backendPostgresDB
struct. 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
Server
implementation will be replacing the type of one member with the newinterface
:type Server struct { db store.Store }
Dependencies / infrastructuređ
The dependencies section should answer questions like:
- What programming language(s) will you use?
- On what hardware or service does the code run?
- Where will persistent data live?
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. 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.
Securityđ
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?
- e.g., in a web app, requests from the userâs browser cross a trust boundary, as the web server shouldnât assume input from the browser is safe.
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.
Security
RecencyBank 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.
Privacyđ
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:
- What sensitive data does your system handle?
- How long will you retain it?
- Who will have access to it?
- How will you protect it?
- e.g., will the data be encrypted at rest and in transit?
Privacy
RecencyBank 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.
Legal considerationsđ
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 compliance
Our 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 FizzlePerfect⢠data within RecencyBank without contract renegotiation.
Loggingđ
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:
- What critical events does the service log?
- Are there different log levels?
- e.g., informational, warning, error, critical
- Where does the system store its logs?
- How long do you retain your logs?
- Who has access to the logs?
- Is there any sensitive data you must keep out of the logs?
Logging
RecencyBank logs the following events:
- At initialization, logs the parameters used to initialize RecencyBank as well as RAM capacity and usage on the host.
- Persisting a value in memory fails.
- Invalidating the cache fails.
Open issuesđ
As you write your design doc, youâll likely encounter at least one of the following situations:
- Thereâs a flaw in your design, but youâre not sure how to solve it.
- Thereâs a gap in your design, but you need to gather more information to close it.
- Youâre torn between multiple solutions.
Create an appendix in your design doc called âOpen Issuesâ that documents your outstanding issues.
Each entry in the open issues section should explain:
- Whatâs the problem that requires more work?
- What options do you see for resolving the issue?
- What is the immediate next step for resolving the issue?
See my companion post for details about how to manage open issues during the review phase.
Open Issue: Choosing RAM size for cache
We 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.
Resolved issuesđ
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]
Alternatives consideredđ
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 were appealing, but I disliked the platform lock-in and the difficulty of testing locally.
Driving Your Design Doc through Reviewđ
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:
How it works
Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content â general knowledge won't be enough. Score 70+ to count toward your certificate.
Questions are cached â you'll always get the same 5 for this article.