Writing ADRs That Actually Inform Decisions
TL;DR — Most ADRs are theatre. They get written after the decision, hidden in a wiki, and never reread. A good ADR is written before the decision is final, captures the trade-offs honestly, and ends up cited in code reviews two years later. The format matters less than the discipline.
Architecture decision records have been around since Michael Nygard’s 2011 post, and the concept is solid. The execution in most companies is not. I’ve seen ADR libraries that read like marketing copy, ADR templates with twenty-three sections, and ADRs that were clearly written after the deploy by someone who lost a bet. None of them did the job an ADR is supposed to do.
The job is simple. An ADR exists so that someone six months or six years from now can understand why a decision was made, what the alternatives were, and what conditions would justify revisiting it. That’s it. Anything that doesn’t serve that job is decoration.
This post is about the structural choices that make ADRs actually useful: when to write them, what to put in them, who reviews them, where they live, and how to keep them honest. I’ll give you the template I use, the rubric I score by, and the failure modes I’ve watched kill ADR cultures in three different companies.
What an ADR is for, precisely
An ADR captures one decision. Not a design. Not a plan. Not a status update. One decision, with the context that justified it and the consequences that followed.
The distinction matters because it constrains scope. “How we’re building the new billing service” is not an ADR. “Why we chose PostgreSQL over DynamoDB for the billing event store” is. “Our hiring strategy for Q1” is not an ADR. “Why we’re hiring a platform engineer before a second backend hire” might be, if framed sharply.
If you can’t reduce the title to a single decision in fifteen words, you’re trying to do too much. Split it into multiple ADRs. Smaller is better. Five ADRs of one page each are infinitely more useful than one ADR of five pages.
The template I actually use
Templates proliferate. The original Nygard template has nine variations on the web. I’ve simplified mine over the years to seven sections. Anything more is rarely read. Anything less and you start losing important context.
# ADR-NNNN, <decision in one sentence>
**Status:** Proposed | Accepted | Deprecated | Superseded by ADR-XXXX
**Date:** YYYY-MM-DD
**Deciders:** <names of people who signed off>
**Consulted:** <names of people whose input shaped this>
## Context
What forces are in play? What's the problem and what constrains the solution?
Three or four paragraphs. Past tense. Factual. No advocacy yet.
## Decision
We will do X.
One paragraph. Active voice. Specific enough that two engineers could
independently implement it the same way.
## Alternatives considered
For each rejected option (at least two):
- **Option name:** one-line summary
- **Why we didn't pick it:** specific, not "less flexible"
## Consequences
- What becomes easier
- What becomes harder
- What we're committing to operationally (cost, ops burden, etc.)
- What we'll need to revisit if X happens
## Open questions
Anything we deliberately deferred. List them with owners.
## References
Links to prior ADRs, RFCs, benchmarks, design docs that informed this.
Seven sections. About one page per ADR. If yours is longer than two pages, you’re either combining decisions or you haven’t decided what the real decision is.
When to write one
Not every decision deserves an ADR. The bar I use: would a smart engineer joining the team in nine months be confused or annoyed if they couldn’t find written reasoning for this?
Concrete triggers that justify an ADR:
- You’re introducing a new dependency that the team will own for years (a database, a queue, a framework).
- You’re choosing between two reasonable options and the choice is not obvious from first principles.
- You’re departing from a previously documented pattern.
- You’re making a decision that will be expensive to reverse.
- The same argument has come up twice in unrelated meetings.
Triggers that do not justify an ADR:
- A bug fix, no matter how clever.
- A library version bump.
- A naming convention (write a style guide instead).
- A decision that was already obvious to everyone in the room.
The fifth trigger above is the most overlooked. When the same architectural debate keeps recurring, that’s a signal that the decision was never properly captured. Writing the ADR ends the recurrence. That alone is worth the hour.
The review process that keeps ADRs honest
ADRs without a review process degrade fast. The first few are great, then the bar slips, then they become a checkbox, then they stop getting written. The process I’ve seen work:
Step 1, propose in draft
The author writes the ADR with status “Proposed” and shares it with the deciders. Deciders are the people whose teams will own the consequences. Three to five is the sweet spot. More than seven and you’ll never get consensus.
Step 2, async comments first
Give it forty-eight to seventy-two hours for written comments. The author responds to each one in the document. This step kills more bad ADRs than any meeting ever did. If you can’t defend a position in writing, the position usually doesn’t survive scrutiny.
Step 3, optional sync review
If there’s substantive disagreement after the async pass, schedule a forty-five-minute review. Not a status update. A working session where the disagreements get resolved or escalated.
Step 4, accept or kill
Either the deciders sign off (status becomes “Accepted”) or the proposal is rejected. Rejected ADRs do not get deleted. They get marked “Rejected” with a one-paragraph reason and kept in the library. The rejections are sometimes more useful than the acceptances.
Step 5, link from code
The most important step, and the one most often skipped. The ADR gets referenced from the code or design docs it influences. A comment at the top of the relevant module that reads // See ADR-0042 for why this uses Postgres advisory locks is worth more than ten team meetings.
A scoring rubric for ADR quality
I use a simple six-point rubric when reviewing ADRs. It catches the most common quality issues:
| Criterion | Pass means |
|---|---|
| Single decision | Title is one sentence, decision section is one paragraph |
| Honest alternatives | At least two real options, each rejected for a specific reason |
| Concrete consequences | Includes ops burden, cost implication, or revisit trigger |
| Identified deciders | Named people, not “the team” |
| Written before commit | Status was “Proposed” before code shipped |
| Linked from code | At least one reference back from the implementation |
An ADR that scores six out of six is a real ADR. Most first drafts score three or four. The two most common gaps are honest alternatives (the rejected options are strawmen) and being written after the fact.
Where ADRs should live
I have strong opinions here. ADRs belong in the repository they describe, in a docs/adr/ directory, in version control, in Markdown. Not in Confluence. Not in Notion. Not in a wiki.
Reasons:
- Code review brings ADRs into the same workflow as code, which is where engineers actually look.
- Version control gives you provenance. You know when the ADR was written, by whom, and how it evolved.
- Markdown is portable. Wikis die. Repos persist.
- Search tools like
grepandripgrepwork. Confluence search does not.
For cross-cutting decisions that span multiple repos, I use a single architecture-decisions repo. The first ADR in that repo is always ADR-0001 explaining why the repo exists and how to write one.
Michael Nygard’s original post is still the canonical reference, and the ADR GitHub organization collects useful tooling. Both are worth a read if you want background.
A worked example
To make this concrete, here’s an abridged real ADR I wrote two years ago, lightly redacted:
# ADR-0023, Use Postgres advisory locks for the order claim flow
**Status:** Accepted
**Date:** 2023-08-14
**Deciders:** Backend lead, platform lead, ops lead
**Consulted:** DBA, two senior backend engineers
## Context
Orders enter a pending state and must be claimed exactly once by a worker
in our fulfillment cluster. We have ten workers and expect peaks of ~200
claims per second. Today we use a naive SELECT...FOR UPDATE SKIP LOCKED
which works but produces lock contention spikes that surface as p99
latency outliers during traffic bursts. We need lower contention and
predictable latency.
## Decision
We will use Postgres advisory locks, keyed on order ID modulo a partition
count, to coordinate claims. Workers will pg_try_advisory_xact_lock
on the partition and then SELECT the next pending order in that partition.
## Alternatives considered
- **Redis-based distributed lock (Redlock or similar):** rejected because
it introduces a new operational dependency for a problem that lives in
the same database that holds the orders. Failure modes are now split
across two systems.
- **Kafka topic with consumer groups:** rejected because the volume
doesn't justify the operational cost, and we'd still need a way to
retry orders that fail after claim.
- **Status quo with SKIP LOCKED:** rejected because the latency outliers
are already a customer-visible problem.
## Consequences
- Easier: claim contention drops materially in load tests
- Harder: advisory locks are a Postgres-specific feature, locks us in
- Operational: DBA must monitor pg_locks for advisory lock count
- Revisit if: we move to a different database, or claim volume exceeds 1k/s
## Open questions
- Partition count tuning, currently set to 32, may need adjustment
## References
- Load test results: <link>
- Postgres docs on advisory locks: <link>
- Prior incident postmortem: INC-2023-08-04
This ADR is one page, takes three minutes to read, and is concrete enough that the next engineer who joins the team can implement against it without asking what we were thinking.
Common Pitfalls
- Vague rejections. “Less scalable” or “more complex” are not real reasons. The reason is specific or it isn’t a reason. If you can’t articulate it, you didn’t evaluate the option seriously.
- Status never updates. ADRs from 2019 still marked “Proposed” because nobody ever closed the loop. Once a quarter, sweep the directory and update statuses or mark abandoned ones as “Withdrawn.”
- Decision section that’s actually a design doc. If your decision section is more than two paragraphs, you’ve conflated decision with design. Split them.
- No one named. “The team decided” is not a decider. Real names. If you can’t get a name to attach, you can’t accept the ADR.
- Written after the code shipped. This isn’t an ADR, it’s history. Useful, but not the same thing. If you find yourself doing this, write it as a “retrospective ADR” and label it clearly.
When This Goes Wrong
ADRs stop getting written. Two months in, the cadence dies. Diagnosis: usually the review process became a chokepoint, or the deciders weren’t responsive. Fix: tighten the SLA. Reviews must close in seventy-two hours or the deciders are auto-replaced.
Every PR triggers an ADR debate. The opposite problem. Diagnosis: the trigger criteria aren’t clear, so people propose ADRs for things that don’t need them. Fix: post the five triggers I listed above in your team’s contribution guide.
Old ADRs contradict current practice. You shipped contrary to ADR-0017 and never updated it. Diagnosis: nobody owns the library. Fix: assign an ADR steward, rotating quarterly, whose job is to flag drift and propose updates or supersessions.
Wrapping Up
ADRs are not paperwork. They’re institutional memory. The team that writes them well makes the same argument twice and never again. The team that writes them poorly relitigates every decision when someone new joins.
The template at the top of this post is yours to use. The review process is the part most teams skip and the part that matters most. The discipline of writing the ADR before the code ships is the discipline that separates teams that learn from teams that just ship.
Start with one ADR this week. Pick a decision your team made in the last sprint that you suspect will be questioned in three months. Write it up. Share it. See what happens. The feedback you get on that first one will teach you more about your team’s decision-making than any retrospective.