Architectural Decision Records (ADRs)
Writing down significant architecture decisions and the reasoning behind them, so future maintainers understand why a choice was made, not just what was chosen.
What is it?
Six months after a team decides to use a message queue instead of direct API calls between two services, a new engineer joins, looks at the code, and wonders: "why is this so much more complicated than just calling the other service directly?" Nobody left a trace of the reasoning — maybe there was a good reason (the other service was unreliable and needed to be decoupled), or maybe it was a mistake that's now safe to undo. Without a record, both possibilities look identical, and the team either lives with a decision they don't understand or risks reverting something that was actually load-bearing.
An Architectural Decision Record (ADR) is a short, standalone document — usually just one file per decision — that captures a single significant architectural choice: the problem or context that prompted it, the options that were considered, the decision that was made, and the consequences (including tradeoffs accepted). ADRs are typically kept right in the codebase (often a folder like docs/adr/), numbered in order, and treated as immutable history — if a decision is later reversed, you write a new ADR that supersedes the old one rather than editing the old one out of existence, so the historical reasoning is never lost.
The key shift in mindset: most documentation describes the system as it is now. An ADR deliberately preserves a snapshot of reasoning at the moment a decision was made, including the constraints and alternatives that no longer apply today — because that context is exactly what's needed to judge whether the decision is still right, or safe to revisit.
Explain like I'm 10
A doctor's chart doesn't just record a patient's current medication — it records why a medication was prescribed and what alternatives were ruled out at the time, so a different doctor later can tell whether circumstances have changed enough to reconsider it.
Examples
A minimal ADR
# ADR-0007: Use a message queue between OrderService and ShippingService
## Status
Accepted
## Context
ShippingService has intermittent downtime during deploys (roughly
weekly). OrderService currently calls it synchronously via HTTP, so
those deploys cause order placement to fail for end users.
## Decision
Introduce a message queue (SQS) between the two services. OrderService
publishes an "OrderPlaced" event and returns immediately; ShippingService
consumes it whenever it's available.
## Alternatives considered
- Retrying the HTTP call with backoff — rejected, still fails if the
outage outlasts the retry window.
- Making ShippingService's deploys zero-downtime — rejected for now,
would require infra work outside this team's control.
## Consequences
- Order placement no longer fails due to ShippingService downtime.
- Order and shipment creation are no longer strictly synchronous —
there's a small delay, and OrderService needs a way to show
"processing" status to the user in the meantime.Notice this isn't just 'what' (use a queue) — it records the actual problem, the alternatives that were seriously considered and why they were rejected, and the honest tradeoffs accepted (a small delay is now introduced).
Structuring ADRs as they accumulate
docs/adr/
0001-use-postgres-for-primary-storage.md
0002-adopt-monorepo-for-frontend-and-backend.md
0007-use-message-queue-between-order-and-shipping.md
0012-supersede-0007-move-to-synchronous-calls-with-circuit-breaker.md
// ^ ADR-0007 is not deleted or edited — 0012 references and
// supersedes it once circumstances changed, keeping the
// original reasoning intact for anyone looking back.ADRs are numbered and kept permanently, even after being superseded, so the history of why the architecture evolved the way it did is never lost — only added to.
How it works
An ADR is written at the moment a significant, hard-to-reverse decision is made — typically as part of the same pull request or discussion that implements it, while the context is fresh. It follows a small, consistent template (context, decision, alternatives, consequences) so any team member can write and read one quickly. Crucially, once written, an ADR is not edited to reflect new information — if the decision changes later, a new ADR is written that references and supersedes the old one, so the full history of reasoning stays intact and in order.
Why does it exist?
It exists because the reasoning behind a decision decays far faster than the decision's effects in the code — the code stays, but the constraints, alternatives, and tradeoffs that justified it live only in people's memories, which fade or leave the company. Without a record, every future engineer either has to blindly trust past decisions forever, or risk re-litigating (and possibly reverting) decisions that were actually correct given context they can no longer see.
When to use it
Write an ADR for decisions that are expensive to reverse, affect multiple teams or a large part of the codebase, or involved real tradeoffs between competing options — choice of a database, a major architectural pattern, a significant third-party dependency, a cross-service communication style.
When not to use it
Don't write an ADR for routine, easily-reversible implementation decisions (a function's internal algorithm, a variable naming convention) — that's what code comments or regular documentation are for. Writing an ADR for every small decision buries the genuinely important ones in noise and makes the practice feel like bureaucratic overhead instead of a useful record.
Common mistakes
Writing an ADR that only states the decision without the context and rejected alternatives — losing exactly the information that makes ADRs valuable later.
Editing or deleting old ADRs to 'keep them up to date' instead of writing a new ADR that supersedes the old one, destroying the historical trail.
Writing ADRs so long and formal that nobody keeps up the practice — the format should stay lightweight enough to actually get used consistently.
Practice exercises
- Easy:
Write a short ADR for a real or hypothetical decision to use TypeScript instead of plain JavaScript for a new project, including at least one rejected alternative.
- Medium:
Find (or recall) a significant technical decision made on a project you've worked on that was never documented. Reconstruct an ADR for it as best you can, noting what context you had to guess at.
- Hard:
Write two linked ADRs: one deciding to build a feature as a monolith module, and a second, later ADR that supersedes it by deciding to extract that module into a separate service, referencing what changed to justify the reversal.
Interview questions
What is an Architectural Decision Record?
A short document capturing one significant architectural decision — the context that prompted it, the alternatives considered, the decision made, and its consequences — kept as a permanent record for future maintainers.
Why shouldn't an old ADR be edited when a decision is later reversed?
Because the old ADR preserves the reasoning and constraints that were true at the time the original decision was made; editing it destroys that historical context. Instead, a new ADR is written that explicitly supersedes the old one.
What kinds of decisions are worth writing an ADR for?
Decisions that are expensive or risky to reverse, affect a significant part of the system or multiple teams, and involved real tradeoffs between alternatives — not routine, easily-reversible implementation details.