Running a design review
A design review is where a team finds the expensive mistakes while they are still cheap to fix. This lesson turns the thinking loop from What system design really is into a meeting you can lead: what to send before it, what to ask at each step, how to disagree well, and how to write the decision down so it stays decided. The three case studies that follow in this course are each run as one of these reviews.
By the end you can
- Run a 45 to 60 minute review with a time budget for each step
- Write a design doc and an architecture decision record (ADR) from a template
- Read an engineer's architecture diagram and spot what is missing
- Pick the right question for each area: data, scale, failure, security, operations, cost
- Turn a disagreement into a condition you can test, then decide and move on
The everyday picture
A week before a new grocery store opens, the area manager walks through it with the building contractor, the IT person and the new store manager. They do not rebuild anything. They walk the customer's path from the door to the checkout, with a checklist in hand. Where does the queue go on the eve of a big festival? What happens to the tills when the power goes out? Where does the truck from the warehouse unload, and does it block the entrance? Each question takes a minute or two.
Everything they find goes on a list, with a name and a date next to each item. The walk takes an hour. Moving a checkout counter after opening day takes a month and annoys every customer. A design review is the same walk, done on a document instead of a shop floor. You follow the user's path through the design, ask what happens when each part fails, and leave with a written list of decisions and owners.
What a review is for, and your role in it
A design review has three jobs. It finds the risky parts of a plan before code exists. It makes the important decisions visible, so they are made on purpose. And it gives everyone the same picture of the system. It is not a status meeting, a demo, a vote, or an exam for the person who wrote the design.
A good review has clear roles, and naming them in the invite prevents most chaos:
- Author. Writes the design doc and presents it. Usually the engineer who will build it.
- Reviewers. Two to five people who read the doc before the meeting. Include someone who will run the system in production and someone from a team that the design touches.
- Decider. One named person, usually the tech lead, who makes the call if the room does not agree. Without a decider, disagreements end in silence and come back next month.
- Timekeeper and note-taker. Often the PM. The note-taker writes decisions and actions live, where everyone can see them.
If you are the product manager (PM), your role is specific. You own the problem, the requirements, the numbers and the priorities. You make sure the hour is spent on the risky parts, and that the meeting ends with a decision and owners. You do not need to be the best engineer in the room. Your strongest tool is a precise question, asked at the right moment. On requirements, speak first. On technical choices, ask questions and speak last, so engineers say what they really think before they hear what the PM prefers.
Not every ticket needs a review. Hold one when a change is hard to undo (a new data store, a core table, a public API), when it touches money or stock, when it crosses teams, or when it will take more than about two weeks to build. For smaller changes, a half-page note in the pull request is enough.
Quick check
Which of these changes most clearly needs a full design review?
A new data store is hard to undo, touches orders (money and stock) and crosses teams, so it meets several triggers at once. A wording change and a cache lifetime are cheap to reverse in the next release, so a short note in the pull request is enough.
Before the meeting
Most of a review's value is created before anyone enters the room. The author shares the doc one or two working days ahead. Reviewers leave comments in the doc itself. The author answers the easy comments in writing, so the meeting is spent on the hard ones.
The invite states the ask: the decision you need and the parts you want reviewed. "Decide: Postgres job table or BullMQ for report jobs. Review: what happens when the report worker crashes." An invite that says "Discuss report design" produces a discussion, and discussions do not end.
If people often arrive without reading, start the meeting with eight minutes of silent reading. Some companies, Amazon among them, start meetings this way with a written memo. It feels strange the first time, and it works better than a slide deck, because a written doc cannot hide a missing number behind a nice picture.
Quick check
Which line in an invite makes it most likely that the meeting ends with a decision?
It names the decision the room must make, so everyone knows what success looks like and can prepare for it. The other two invite a conversation with no finish line, and conversations do not end on their own.
The agenda: the thinking loop with a clock
The review follows the same loop you met in What system design really is. The difference is the clock. Each step gets a fixed budget, and the timekeeper says out loud when it runs out. The deep dives get the biggest share, because that is where designs usually fail.
What each step produces
- Requirements (5 min). The author reads back the functional requirements, the non-functional ones with numbers, and the non-goals. Ask: "Which of these is the hardest to meet?" That answer often picks the deep-dive topic.
- Numbers (5 min). Peak requests per second, rows per day, size in one and three years (Estimation). Ask: "Can you show me the arithmetic?" A number without its arithmetic cannot be checked.
- API and data (10 min). The main endpoints, the tables, their keys and indexes (APIs done right, Data modeling, Indexes). Ask: "Which unique key stops a retry from creating a duplicate?"
- Big picture (5 min). One diagram. The author walks one real request through it, out loud, from the phone to the database and back.
- Deep dives (15 min). One or two of the hardest parts, chosen by the decider before the meeting. For stock, it is usually "two people buy the last unit". For sync, "the upload succeeded but the reply was lost".
- Failure modes (10 min). Cover each box with your finger and ask what the user sees (Timeouts and retries). Write the answers in a table.
- Trade-offs (5 min). The options considered, the one chosen, and what the team gives up by choosing it. The decider decides.
- Rollout and ops (5 min). The migration plan (Safe migrations), the release plan and rollback (Shipping safely), the dashboards and alerts (Observability), and the monthly cost.
For a 45-minute slot, cut each step a little and keep the shape: 4, 4, 6, 4, 12, 7, 4 and 4 minutes. Never cut the deep dives and failure modes to make room for a longer presentation.
Two tools keep the clock honest. The first is a parking lot: a list on the shared screen where off-track topics go, each with an owner, to be handled after the meeting. The second is one sentence the timekeeper can always say: "We have two minutes left on this. What fact would settle it, and who can get it by Thursday?" Most stuck debates are missing a fact, and nobody in the room has it.
The design doc
A design doc is the input to the review. A good one is three to six pages. Longer docs go unread, so details belong in an appendix. The order of the sections matters: everyone reads the top, and only engineers read the middle closely.
The summary and ask is the first paragraph: what we are building, why now, and what decision we need. The goals and non-goals stop scope from growing in the meeting. The alternatives section is the one most often skipped, and the most useful. If a doc presents only one option, nobody can tell whether it was chosen or was only the first idea. The open questions section is where the author writes what they do not know yet, instead of hiding it.
How to read an engineer's diagram
Architecture diagrams look complete because boxes are neat. The risk usually hides in the arrows and in what is not drawn. Use the same routine on every diagram:
- Find the user and follow one request. Say it out loud: "The cashier scans, the app calls the API, the API reads Postgres, the answer comes back." If nobody can narrate it, the diagram is decoration.
- Ask four things about every arrow. What moves along it? How often? Does the caller wait for the answer (synchronous) or not? What happens when it fails? An unlabelled arrow hides most of the risk in a design.
- Ask three things about every box. How many copies run? What does it keep in memory or on disk? Which team owns it at 3 am? A box that holds no state is cheap to copy (Scaling up vs scaling out). A box that holds state needs a backup and a recovery plan (Backups).
- Find the source of truth. For each kind of data, one place is the source of truth. If both Redis and Postgres hold stock numbers, which one wins when they disagree? (Caching, CAP and consistency)
- Cover each box with your finger. What does the user see now? Slower pages, an error, a stuck spinner, or wrong data? Wrong data is the worst answer, because nobody notices it.
- Look for what is missing. Authentication, rate limits, monitoring, backups, the dead-letter queue, and the admin screen someone will need on day two.
Quick check
You cover the Redis cache box with your finger and ask what the user sees. Which answer should worry you most?
Wrong data is the worst failure because nobody notices it: no alert fires, and customers and staff act on it. A slower page and a visible error are bad too, but they are honest, so someone will see them and fix them.
Think first: the design has six boxes, no failure section, and you have ten minutes. Where do you start?
Start with the boxes that the user's main action cannot work without. Cover them one at a time, in the order a request passes through them, and ask what the user sees. Then do the outside services, such as a payment API or an SMS gateway, because the team does not control them and they fail in slow, strange ways.
For each answer, write one line in a table: box, what fails, what the user sees, what we do about it. Ten minutes is enough for six rows. The empty cells are your action items.
A question bank by area
You do not need to ask all of these. Before the meeting, pick the two areas where this design is most likely to hurt, and bring three questions from each. The lesson links let you refresh the idea behind each question.
| Area | Ask | Lessons |
|---|---|---|
| Requirements | Which user action is this for? Which NFR is the hardest to meet? What is out of scope? | System design, p99 and SLOs |
| Numbers | What is the peak QPS on the busiest day, and how did you get it? How big is it in three years? | Estimation |
| Data | Which unique key blocks duplicates? Which index serves each main query? How do we change this schema without downtime? | Data modeling, Indexes, Safe migrations |
| Consistency | How old can this data be before it causes a wrong decision? What happens if two people change it at once? | Transactions, CAP and consistency |
| Scale | What breaks first at twice the load? Is the API stateless? What do we cache, and how do we clear it? | Replication, Scale up vs out, Caching |
| Failure | What does the user see when this box is down? Does every call have a timeout? Where do failed jobs go? | Queues and jobs, Timeouts and retries |
| Events | What happens if this message is processed twice? Is the event written in the same transaction as the data? | Queues and jobs, Outbox and sagas |
| Security | Who can call this, and which rows can they see? Where do the secrets live? What personal data do we store? | Security |
| Operations | How do we know it is broken before users tell us? Who gets the alert? How do we roll back? | Backups, Observability, Shipping |
| Cost | What does it cost per month today and at three times the load? How many hours a month will it take to run? | System design, Data stores |
| Stores and mobile | What happens with no internet? Which old app versions are still in use, and do they keep working? | Offline sync, Shipping |
Disagreeing well
Good engineers disagree, and that is the point of a review. The problem is a disagreement with no way to end it. Five habits help.
Turn positions into conditions. Ask each side: "What would make you change your mind?" Good answers are specific: a number ("if we pass about 200 jobs a minute"), a feature ("if we need delayed jobs"), or a test result. Once both sides have named their conditions, the argument becomes a question about facts. If nobody can name a condition, the choice is a matter of taste. Let the decider pick, and move on.
Ask for the cheapest test. Many debates can be settled in two days with a spike, which is cheaper than another hour in a meeting room.
Know which doors lock behind you. Amazon describes decisions as one-way and two-way doors. A one-way door is hard to reverse: a public API, a core table, a new data store. A two-way door is easy to reverse: a cache TTL, a library inside one module. Spend review time on one-way doors and decide two-way doors quickly.
Disagree, then commit. When the decider decides, everyone builds it fully, including the people who argued against it. Their view goes into the ADR, with the condition that would reopen the decision. People commit more easily when they know their concern is written down and has a trigger.
Watch for bikeshedding. Bikeshedding is a long debate on an easy topic, such as a table name or a folder layout, because everyone can have an opinion on it. When you hear it, put it in the parking lot. Keep comments about the design, never the person: "this loses the job if the worker crashes", not "you forgot about crashes".
Quick check
Which of these decisions is a one-way door that deserves slow, careful review time?
Once other people's software depends on a public API, every change needs a migration for every caller, so it is hard to reverse. Worker counts and retry limits change in a normal deploy, so they are two-way doors: decide them quickly and adjust later.
Recording the decision: the ADR
An ADR (architecture decision record) is a one-page note about one decision. The format most teams use comes from a 2011 blog post by Michael Nygard: a title, a status, the context, the decision and its consequences. Many teams add the options considered and the conditions for revisiting it.
Keep ADRs in the code repository, numbered, next to the code they describe, for example docs/adr/0007-report-job-queue.md. Do not edit an accepted ADR when you change your mind. Write a new one that says "Supersedes ADR-0007", and mark the old one as superseded. A year later, a new engineer can read why the system looks the way it does, and the team stops having the same debate every six months.
Build it
We will make a design doc template, an invite with a clear ask, an ADR template and a filled ADR, and a one-page review checklist. The Practice section then has you fill a failure table and write an ADR of your own.
-
Save the design doc template (top half)
Keep it in your team's docs or in the repository as
docs/design/TEMPLATE.md. The comments say what a good answer looks like. Delete them as you fill the doc.Markdown# Design: <feature or system> Author: <name> Reviewers: <names> Decider: <name> Status: draft | in review | accepted Review date: <yyyy-mm-dd> ## 1. Summary and ask <!-- 3-5 sentences: what we build, why now, and the decision we need. --> ## 2. Context <!-- What exists today and what hurts. Link the requirements sheet. --> ## 3. Goals and non-goals - Goal: - Non-goal: <!-- things a reader might expect that we will NOT do --> ## 4. Requirements - FR1: <role> can <action> - NFR: <quality> <number> measured at <where> ## 5. Numbers <!-- Show the arithmetic: users x actions / 86,400 x peak factor. --> ## 6. API <!-- Method, path, request, response, errors, idempotency. --> ## 7. Data model <!-- Tables, keys, unique constraints, indexes, size in 1 year. --> -
Save the bottom half
The failure table and the alternatives are the two sections reviewers will check first, so the template makes them hard to skip.
Markdown## 8. Architecture <!-- One diagram. Label every arrow: what moves, how often, sync/async. --> ## 9. Deep dives <!-- The 1-2 hardest parts, in detail. --> ## 10. Failure modes | Part fails | What the user sees | What we do | |------------------|--------------------|--------------------| | | | | ## 11. Alternatives considered <!-- At least two real options, with why we did not pick them. --> ## 12. Security and privacy <!-- Who can call what, which rows they see, personal data, secrets. --> ## 13. Rollout, monitoring and cost - Migration steps (expand / migrate / contract): - Release: flag, pilot stores, rollback: - Dashboards and alerts (SLO): - Cost per month today / at 3x: ## 14. Open questions - [ ] <question> (owner, due date) -
Write the invite with the ask
One short message. It names the decision, the roles, and what to read. People who read this invite arrive knowing why they are there.
OutputSubject: Design review: report jobs (Thu 3:00-4:00 pm) Doc: docs/design/report-jobs.md (please comment by Wed 5 pm) Decide: Postgres job table or BullMQ for report generation. Review: what happens when a worker crashes mid-report; load on the main database on the 1st of the month. Decider: tech lead Timekeeper + notes: PM Out of scope: report layout and Excel formatting. -
Save the ADR template
Short on purpose. If an ADR needs more than a page, the extra belongs in the design doc, linked from the ADR.
Markdown# ADR-<number>: <decision in a few words> Status: proposed | accepted | superseded by ADR-<n> Date: <yyyy-mm-dd> Decider: <name> Design doc: <link> ## Context <!-- The forces: numbers, constraints, what hurts today. --> ## Options considered 1. 2. ## Decision <!-- What we chose, in one or two sentences. --> ## Consequences <!-- Good, and the costs we accept. --> ## Revisit when <!-- Concrete conditions that reopen this decision. --> -
Fill in the ADR from the review
This is the ADR from the Mangrove Market callout above. Notice that every claim has a number, the costs are written as clearly as the benefits, and the dissent has a trigger instead of being forgotten.
Markdown# ADR-0007: Use a Postgres job table for report generation Status: accepted Date: 2026-10-08 Decider: tech lead ## Context - Managers generate sales reports in Back office. Today the report is built in the HTTP request; big regions take 40-90 s, the load balancer cuts at 60 s, and users press Generate again. - About 300 reports/day; peak about 120 in the first hour of the 1st. Each takes 20 s to 4 min and reads the reporting replica. - We run Postgres and NestJS. Our only Redis is a cache with eviction on (allkeys-lru), so it cannot hold jobs safely. ## Options considered 1. Postgres `job` table claimed with FOR UPDATE SKIP LOCKED. 2. BullMQ on a new Redis with AOF and noeviction. 3. Keep it in the request with a 5 min timeout. Rejected: holds connections, and retries create duplicate reports. ## Decision Option 1. Two worker processes, at most 4 reports at once, 3 attempts with backoff, dedupe_key = report type + period + zone. ## Consequences + No new system to run, back up and monitor. + Job row is inserted in the same transaction as the report row. - We write retries and the stale-job reaper ourselves (about 150 lines). - Idle polling adds about 2 tiny queries per second on the primary. - No built-in UI; we add a "jobs by status" panel to Grafana. ## Revisit when - More than about 5,000 jobs/day, or we need delayed or rate-limited jobs (e.g. SMS), or the claim query enters the top 10 of pg_stat_statements. BullMQ is then the default candidate. - Dissent: one backend engineer preferred BullMQ for its UI; agreed to the triggers above. -
Print the one-page review checklist
Bring it to every review. Tick only what you checked in the doc or heard in the room, not what someone promised to think about.
Markdown# Design review checklist ## Before - [ ] Doc shared 1-2 working days ahead; comments answered - [ ] Invite states the ask; decider, timekeeper, note-taker named - [ ] Deep-dive topics chosen (at most 2) ## Requirements and numbers - [ ] Every NFR has a number and a place it is measured - [ ] Peak QPS and size in 1 and 3 years, with the arithmetic - [ ] Non-goals written down ## API and data - [ ] Unique keys stop duplicates; POSTs that create things are idempotent - [ ] An index for each main query; schema change works with old + new code ## Architecture and failure - [ ] Every arrow labelled: what, how often, sync or async - [ ] Source of truth named for each kind of data - [ ] Every box covered: what the user sees, what we do - [ ] Timeouts, retries with backoff, dead-letter queue, alerts ## Security, ops and cost - [ ] Access scoped by user and site; secrets outside the code - [ ] Dashboards and alerts tied to an SLO; on-call owner - [ ] Rollout with a flag and a rollback; backups covered - [ ] Monthly cost today and at 3x ## After (last 5 minutes) - [ ] Decisions read back; an ADR owner for each - [ ] Actions and parking-lot items have an owner and a date
Bring it to your team
These questions run the meeting itself. Use them alongside the question bank above.
- "What decision do we need to leave this room with today?"
- "If we do not agree, who makes the final call?"
- "Can you walk me through one request, from the phone to the database and back?"
- "Which part of this design are you least sure about? Let's spend the deep dive there."
- "What would make you change your mind?"
- "Who writes the ADR, and by when?"
Red flags
- The doc arrives an hour before the meeting, or is a slide deck with no numbers.
- Only one option is presented, with no alternatives considered.
- Half the hour goes to names, folders or formatting.
- The meeting ends with "let's think about it" and no owner.
- The same debate returns a month later because nothing was written down.
- "We will add monitoring later."
Practice
Fill the failure table for the sample diagram
Go back to the third diagram. Here is what it does: when a click-and-collect order is ready, store staff tap "Ready" in the Store app. The API saves the new status in Postgres and adds a "send ready SMS" job to the queue. A worker takes the job and calls the SMS gateway over HTTPS. Cover each of the six boxes in turn and write one row per box: what fails, what the store staff and the customer see, and what the design should do. Add one more row for the trickiest case you can think of. Give yourself 15 minutes.
- Every box in the diagram has a row: Store app, API, Postgres, queue, worker and SMS gateway
- Each row says what people actually see, not only what breaks inside
- The worker row deals with the risk of sending the same SMS twice
- The SMS gateway row has a timeout, retries with backoff, and a place for jobs that fail every attempt
- At least one row names the alert that tells the team before customers do
- Any cell you could not fill is written down as an open question with an owner
Show a model answer
| Part fails | What staff and customer see | What we do |
|-----------------------|-----------------------------------|----------------------------------------------|
| Store app offline | Staff see "waiting to send" | Keep the tap locally; retry with the same |
| | | request id, so a retry cannot repeat it |
| API down | Staff see "not saved, retrying" | Several copies behind the load balancer; |
| | | alert on the 5xx rate |
| Postgres primary down | Status not saved, no SMS | Fail over to the replica; API answers 503 |
| Queue down | Status saved, SMS late | Outbox row in the same transaction as the |
| | | status; a relay adds the job later |
| Worker crashes | SMS a little late | Job not acked, so it runs again; the job id |
| | | is a dedupe key, so no second SMS |
| SMS gateway down | Customer's SMS is late; the order | 5 s timeout, 3 retries with backoff and |
| | page still says "Ready" | jitter, then dead-letter queue and an alert |
| Gateway sent the SMS, | Risk: the customer gets two SMS | Pass the job id as the gateway's reference |
| but its reply is lost | | if it supports one; if not, accept a rare |
| | | duplicate and say so in the doc (owner: PM) |The last row is the one most teams miss. It is the same "the work happened but the reply was lost" problem that the POS sales case study solves for uploads.
Write the ADR from a review's notes
Imran took these notes in a review of delivery photos for the Rider app. Turn them into ADR-0012, using the template from the Build it section. Keep it to one page. About 15 minutes.
Review: delivery photos for the Rider app, 2026-10-14. Decider: Nadia (tech lead).
Numbers: about 60,000 deliveries a day, one photo each, about 200 KB after compression.
Option A: store each photo in Postgres, in a bytea column.
Option B: the Rider app uploads straight to object storage with a presigned URL;
Postgres keeps only the object key.
Option C: keep photos on the phone and upload them overnight on Wi-Fi.
Discussion: C is too late when a customer disputes a delivery the same evening.
A adds a lot of data to the main database, its backups and its replicas.
Decision: B. Keep photos 180 days, then delete them with a lifecycle rule.
Dissent: Ken (engineering manager) wanted A for "one system to back up";
agreed, as long as the bucket has versioning turned on.- The context shows the numbers with their arithmetic, such as the gigabytes per day
- At least two options, each with the reason it was or was not chosen
- The decision fits in one or two sentences and includes the 180-day rule
- The consequences list what the team gives up, not only the benefits
- "Revisit when" has concrete conditions, never "if needed"
- The dissent is recorded with the condition that was agreed
Show a model answer
# ADR-0012: Store delivery photos in object storage
Status: accepted Date: 2026-10-14 Decider: tech lead
## Context
- The Rider app takes one proof-of-delivery photo per order.
- About 60,000 deliveries/day x ~200 KB = ~12 GB/day, ~4.4 TB/year.
- Disputes come in the same evening, so photos must arrive within minutes.
## Options considered
1. bytea column in Postgres. Rejected: +12 GB/day in the main
database, its backups and its replicas.
2. Presigned upload to object storage; Postgres stores the key.
3. Upload overnight on Wi-Fi. Rejected: too late for same-day disputes.
## Decision
Option 2. Photos are kept 180 days, then deleted by a lifecycle rule.
## Consequences
+ The main database stays small; photo bytes skip our API servers.
+ About 2.2 TB stored at steady state (12 GB x 180 days).
- One more system to secure: private bucket, short-lived URLs.
- A failed upload leaves a delivery with no photo; a nightly job
lists them for the store.
## Revisit when
- Photos must be kept longer than 180 days, or storage cost passes
the monthly budget agreed with finance.
- Dissent: the engineering manager preferred Postgres for a single
backup story; agreed once bucket versioning is on.Check yourself
It is minute 35 of a 60-minute review. The team is still debating whether the table should be called report_job or job, and nobody has discussed what happens when a worker crashes. What do you do?
A table name is a two-way door and a classic bikeshed. The parking lot keeps it without spending the hour on it. Failure modes are where the real risk is. A second meeting wastes everyone's preparation, and the PM picking a technical name skips the decider and teaches the team that the loudest person wins.
Two senior engineers have argued for ten minutes: one wants Kafka for an event feed, the other wants BullMQ. What is the most useful next question?
Asking for conditions turns two positions into a question about facts, such as events per second or the need to replay history. Popularity says nothing about your numbers. A vote ends the debate without a reason, so it returns later. Using both doubles the systems to run.
What belongs in an ADR?
An ADR records why a decision was made and what would change it, in about a page. The design doc holds the details and is linked from the ADR. A one-line decision loses the reasons, which are what a future engineer needs. Minutes record the conversation, not the decision.
Flashcards
Key takeaways
- A review exists to find costly mistakes early and to leave with written decisions and owners.
- Share the doc one or two days ahead with a clear ask, and name a decider, a timekeeper and a note-taker.
- Follow the thinking loop with a clock. Protect the deep dives and the failure modes; they find most of the risk.
- Read any diagram by following one request, questioning every arrow, and covering every box.
- Turn disagreement into conditions with "what would change your mind?", then decide, commit, and record it in an ADR.
Sources
- Documenting Architecture Decisions, Michael Nygard, 2011
- Architectural Decision Records, templates and tools collected by the ADR community
- Design Docs at Google, Malte Ubl, 2020