You open the design doc for a new feature, read it twice, and everything looks fine. The boxes connect, the sequence diagram has happy little arrows, the estimate fits the sprint. Then someone who has been burned a few more times than you asks "okay, and what happens when this breaks at 2 a.m.?", and the room goes quiet. Nobody wrote that part down, because nobody thought about it.
That question comes from a short, fairly stable list that experienced architects run through on most designs, often without saying it out loud. Once you know the list, you can ask the questions yourself, before the code exists and while changing your mind still costs a conversation instead of a migration.
To keep this concrete, we will use one feature across the whole article. Your Rails app sells annual subscriptions and the business wants partial refunds: a customer cancels in month four and gets the unused part of the year back. The plan on the table adds a refunds table, calls the payment provider to issue the refund, emails the customer, updates the monthly revenue report, and backfills historical refunds that support has been issuing by hand in the Stripe dashboard.
What questions should you ask in a software design review?
The questions fall into two families. The first family is about failure and change: what breaks, how far the damage spreads, how you notice, and how you undo it. The second family is about the problem itself: whether the design solves the real need, what the business expects in situations nobody described, and which rules really must hold at every moment. Most reviews cover the boxes and arrows and skip both families. Asking them takes one person willing to do it and a written answer next to the design.
What problem are we actually solving?
Start here, because every other answer depends on it. "Add partial refunds" is a ticket, not a problem. The problem might be churn (customers who cannot cancel cleanly dispute the charge instead), support load (an agent spends twenty minutes per manual refund), or a legal obligation in a market where pro-rated refunds are required. Each of those leads to a different design. Support load might be solved with a better admin screen and no customer-facing feature at all.
Two follow-ups sharpen this question. Ask what is explicitly out of scope, so the design does not grow to cover upgrades, downgrades, and coupons. Then ask which requirement is most likely to change, because that is the part of the model that deserves the cleanest boundary. In the refund example the most volatile rule is usually the refund policy itself: how unused time is counted, whether fees are kept, whether the first month is ever refundable.
What happens when it breaks?
This is the question everyone remembers, and the one most often answered with "we will add retries". Push past that answer by making it specific. What happens when the payment provider times out after it already processed the refund? What happens when the refund succeeds but the email job fails? What happens when step three fails after step two has already committed?
The dangerous failures are rarely full outages. A dependency that is down is easy to notice. A dependency that answers slowly, or returns partial data, or succeeds on its side while your side records a timeout, is the one that leaves money in the wrong place. For refunds, the classic failure is the double refund: the request times out, the job retries, and the customer gets paid twice.
The usual defence is an idempotency key: a unique identifier for the business operation, stored with the result, so a repeated attempt returns the first result instead of doing the work again. Stripe supports this directly through the Idempotency-Key header, which saves the status code and body of the first request for a given key and returns the same result for later requests with that key. Stripe may prune a key once it is at least 24 hours old, so a retry that arrives later is not deduplicated on their side. Your own side needs the same guarantee anyway.
In a Rails app that means a refunds table with a unique index on a NOT NULL idempotency_key column, so two concurrent jobs cannot both insert. Generate the key once per business operation (the first refund for order 42), store it with the refund request, and reuse it on every retry. A key generated inside the retry loop changes on each attempt and protects nothing.
How easy is it to roll back?
Amazon's 2015 letter to shareholders described consequential, nearly irreversible decisions as "one-way doors" and reversible ones as "two-way doors". The same split works for changes in a codebase. For each part of the design, ask what survives a git revert.
Run the refund plan through that filter and the answer changes by component. The new admin screen is a two-way door: revert the deploy and it is gone. The refund sent through the payment provider is a one-way door: reverting the code does not take the money back from the customer. The confirmation email is a one-way door too. The backfill rewrites historical data, so it is one-way unless you keep a copy. A migration that renames a column used by the revenue report can break the report on the next deploy even if nothing else changed.
Naming the one-way doors tells you where the effort goes. Put the refund behind a feature flag so it can be switched off without a deploy. Run the backfill as a dry run first, writing what it would change into a separate table you can inspect. Split the schema change with the expand, migrate, contract sequence described in Parallel Change: add the new column, move the readers over, and only then drop the old one, so each step except the final drop can be undone on its own, and the drop happens only once nothing reads the old column any more. Decisions about data models and partition keys sit at the far end of this scale; they are much harder to reverse than the choice of a library, so they deserve the slowest review.
How big is the blast radius?
Blast radius is how much breaks before you manage to undo the change. Draw the design and, for each box, ask what stops working when that box fails. If the refund service shares a database with checkout and a heavy backfill holds row locks on subscriptions or saturates the database, the blast radius of a refund bug is now "nobody can buy anything".
Blast radius grows through coupling, usually without anyone deciding it should. It comes from things like five services reading the same table, or a synchronous chain of four calls where each one waits for the next. Each of those turns a local bug into a coordinated rollback, with several teams deploying in a fixed order in the middle of an incident. If this sounds familiar, it is the same smell described in why your event-driven architecture may actually be a distributed monolith. For the refund feature, the cheap fix is to read the historical data for the backfill from a replica and write to the primary in small batches, and keep the refund write path out of the checkout transaction entirely.
How will we know it is failing?
Ask how you will observe the system while it is failing, which most dashboards are not built for. Start by defining the steady state in numbers you can check: how many refunds per day, what share of them succeed on the first attempt, how long from cancellation to money returned. Then ask what alert fires when that state drifts, and who receives it.
For refunds, a business metric often shows the problem first. If the number of refunds per day doubles overnight, something is wrong even when every request returns 200. Maybe the eligibility rule is too generous, maybe a retry loop is creating duplicates that your idempotency check does not catch. A reconciliation job that compares your refunds table with the provider's records once a day catches the cases where the two sides disagree, which is exactly the class of failure that logs and error trackers usually do not show.
What happens if it succeeds?
What if the feature works and marketing announces "cancel anytime, get your money back"? Ask for the load shape, not the average: refunds will not arrive evenly, they will spike on the day annual plans renew and after every price change. Ask what grows without limit. Refund records, audit entries, and emails sent are small per row and large per year, so ask how long each of them is kept and who deletes them.
Success also changes who depends on the feature. Once finance builds its monthly close on the refund report, a schema change in that table stops being a local decision, so the refund report needs a stable contract from day one.
How do we get there from here?
Reviews often skip the migration from today's system to the new one, so ask about it directly: which deploy goes first, whether old and new code can run side by side during the rollout, and what happens to the refunds support is issuing by hand while the backfill runs.
Ask also whether the design fits the patterns the team already uses or introduces a new one. A new queue technology, a new framework for background jobs, or a service mesh may be the right call, but each one is something the whole team has to learn and support in production. If the real problem is a missing timeout or a missing idempotency key, fixing that is cheaper than new infrastructure.
How do you ask these questions when business people are in the room?
Everything above works well between engineers. It falls apart in a workshop with the people who own the refund policy, because the honest technical question sounds like gibberish to them. Ask a finance manager "must the refund and the subscription status change atomically?" and you will get a polite nod. Ask "what happens if we do not record this right away?" and they will not know, will imagine something bad, and will ask for everything to be 100% consistent.
The fix is to make the question concrete: name a specific delay and describe the consequence yourself, instead of leaving them to imagine one. Instead of "must this be consistent?", ask "if the system confirmed the cancellation now and checked the refund rules five minutes later, what would go wrong?". Then describe what each variant means for them: "if we check later, a customer might see a confirmation for a refund that we then reduce, and support will get that call". Now they can choose with real information, and their answer tells you where the consistency boundary sits. In the refund example the answer usually is that the amount must be fixed at the moment of cancellation, while the email and the report can trail behind by minutes.
The second business-facing move is the plain "and what then?". Walk through the user stories and ask about situations nobody wrote down. What happens when a customer asks for a refund after the refund window closed? What happens to the money when the card used for the original payment has expired? What does consumer law in this market say about refunds for digital services? Quite often the answer is "we never thought about it", and you have just found a gap that would otherwise have surfaced as an angry customer or a legal letter months after launch.
For the events on the board, a few short questions decide most of the technical design: how often does this change, can it happen twice, and can it be undone. The refund policy changes a few times a year while refunds happen every day, so the policy belongs in a versioned rule rather than in code scattered across the refund flow. "Refund issued" can happen more than once per order, partly, and it cannot be undone, only compensated by a new charge. Those last two answers alone tell you that refunds need their own records with an amount each, not a boolean flag on the order.
Experienced people seem to ask exactly the right question because they already know the possible answers. Only a handful of patterns fit a given problem (there are only a few consistency strategies, and refunds will use one of them), and a good question in business words rules most of them out. Each answer opens the next branch of questions until only one design still fits. You do not need to say "aggregate" or "single source of truth" in the meeting, but you should be thinking about where the consistency boundary is and which single place holds the truth for each fact.
A design review checklist you can copy
Paste this into the template of your next design doc and ask the author to answer each line in one or two sentences before the review meeting:
- What business problem does this solve, and what is out of scope?
- Which requirement is most likely to change?
- What happens when each dependency is slow, partial, or down?
- What prevents a retried operation from running twice?
- Which parts are one-way doors, and how is each one protected?
- What breaks for everyone else when this component fails?
- What is the steady state, and which alert fires when it drifts?
- What happens at ten times the expected load, and what grows forever?
- How do we move from today's system to the new one, step by step?
- Which business rules must hold at every moment, and which may lag behind?
If you want to practice these questions without a real deadline attached, an architectural kata is a good place to do it.
FAQ
What is a one-way door decision in software?
It is a change whose effects survive a code revert: a destructive migration, a backfill that rewrites data, money sent through a payment provider, an email delivered to a customer, a public API contract that clients already use. These need a careful review and a protection plan, such as a feature flag, a dry run, or a backup. Everything that a revert fully undoes is a two-way door and can move faster.
Is a design review the same as a code review?
No. A code review checks how something was built, after it was built. A design review checks what will be built and why, while changing it is still cheap. Several of the questions overlap, rollback and blast radius in particular, but asking them at design time is what lets you avoid the one-way doors instead of labelling them after the fact.
Does every change need a design review?
No. Small two-way-door changes with a local blast radius are cheaper to ship and revert than to review in a meeting. Save the full set of questions for changes that touch money, shared data, public contracts, or more than one team. A quick check of the one-way-door question is enough to tell which kind of change you have.
Who should answer design review questions?
The author of the design writes the first answers, and the people who own the affected areas check them. For the business questions, such as which rules must hold at every moment, the answer has to come from the people who own those rules, phrased as consequences they can judge, not from the engineers guessing on their behalf.
Happy reviewing!
