Software Design Reference Reference · Trade-Offs

Reference document · Master Designer in one page

Trade-Offs Cheat Sheet

At Master Designer level there are no perfect solutions — only forces. For each trade-off: the two forces, the questions that decide it, the default posture when evidence is thin, and — the entry to read in hindsight — the signal that you chose wrong.

How to run a trade-off

Three moves turn an argument into a decision, and they are the same three every time.

  1. Write both columns. Not the one you favour — both, with their hidden costs. Most trade-off arguments are two people each holding one column.
  2. Name the unit. Almost none of these are decided per system. Consistency is decided per operation, sync/async per workflow, build/buy per component. A global answer is usually a refusal to decide.
  3. Name the reversibility. If the choice can be undone in a deploy, pick fast and learn. If it cannot, spend the hour on designing it twice.
Trade-offDecided per…Default when evidence is thinLesson
Build vs Buycomponentbuy, until the differentiator argument is made0027
Abstraction vs Duplicationoccurrenceduplicate, until three copies change together0028
Configuration vs Conventionsettingconvention, until variation is measured0029
Generality vs Specificitymodulespecific, until the second real case0030
Synchronous vs Asynchronousworkflowsync, until a concrete reason demands async0031
Consistency vs Availabilityoperationavailability plus an idempotent write path0032
Coupling vs Coordinationboundaryindependence, with the contract written down0033
Performance vs Maintainabilityhot pathclean, until a number says otherwise0034
Stability vs Speed of Changechangesmall reversible steps — buy both0035
Isolation vs Integrationconceptisolate where the meanings differ0036
Correctness vs Delivery Speedchangespeed if reversible, apparatus if not0037
Short-Term Productivity vs Long-Term Maintainabilityloanborrow if you record it0038

Complexity trade-offs

Build vs Buy

Forces: control and fit (build) vs time and maintenance burden (buy).

Deciding questions: Is this your business's core differentiator? Will a library do 80% now and 80% in three years? Who owns the upgrade path — you or the vendor? Who is paged at 3 a.m. either way?

Default: buy (or take the open-source package) until the differentiator argument is made explicitly. Choose boring technology.

Signal you chose wrong: you built it, and the team's best engineers spend their quarters on a problem no customer has ever mentioned. Or: you bought it, and half the codebase is workarounds for the 20% that never fitted.

Taught in: lessons 0026, 0027

Abstraction vs Duplication

Forces: one source of truth (abstraction) vs freedom to diverge (duplication).

Deciding questions: Will these two things change for the same reason? Is the shape proven by three occurrences, or guessed from one? How often have the copies actually changed together?

Default: duplication with intent beats a wrong abstraction. Wrong abstractions are the expensive kind — Sandi Metz.

Signal you chose wrong: the shared function has grown a kind parameter and a branch per caller. Every new case is a negotiation with the shape, and no caller can change alone.

Taught in: lessons 0002, 0028

Configuration vs Convention

Forces: explicit flexibility (configuration) vs predictable defaults (convention).

Deciding questions: How often does this actually vary per deployment or per tenant? Who is the operator — engineers or non-technical users? Is the convention documented and discoverable?

Default: convention for the common case; configuration only where variation is measured.

Signal you chose wrong: a settings file of forty knobs, of which four have ever been changed — and an incident caused by the thirty-sixth being set to something nobody tested.

Taught in: lesson 0029

Generality vs Specificity

Forces: reusable and future-proof (general) vs simple and fit-for-purpose (specific).

Deciding questions: What is the actual evidence of a second use case? Does the general version make today's case simpler, or only tomorrow's cheaper? What does the generalisation cost in parameters, indirection and tests?

Default: specific until the second use case is real — then generalise, guided by the rule of three.

Signal you chose wrong: the module has parameters no caller passes and hooks nothing hooks. Reading the one real use case means reading past machinery built for cases that never arrived.

Taught in: lesson 0030

System trade-offs

Synchronous vs Asynchronous

Forces: immediate, simple, debuggable (sync) vs decoupled, resilient, scalable (async).

Deciding questions: Must the caller have the result to continue? Can you answer what happens when a handler dies mid-way, when events arrive out of order, when one is delivered twice?

Default: sync unless a concrete reason — latency, load, decoupling — demands async. Async everywhere turns every feature into a distributed-systems problem.

Signal you chose wrong: an incident whose timeline nobody can reconstruct, because the workflow lives in six handlers and no single trace covers it. Or: a queue whose dead-letter count nobody has looked at in a month.

Taught in: lessons 0025, 0031

Consistency vs Availability

Forces: every read sees the last write (consistency) vs every request gets an answer (availability).

Deciding questions: What does a stale read cost here — a retry, an apology, or a financial error? What does a timeout cost? Which of the two failures does the business tolerate better?

Default: don't choose globally — choose per operation. CAP is a framing, not a product picker.

Signal you chose wrong: a customer was shown “in stock” and charged for something that wasn't; or the checkout went down because a read that could have been served from cache waited for a quorum.

Taught in: lesson 0032

Coupling vs Coordination

Forces: independent components (low coupling) vs the standing cost of keeping them aligned (coordination).

Deciding questions: When the contract changes, who must change together? Can one side deploy alone? Does Conway's law already put the boundary at a team edge?

Default: favour independence, and pay the coordination tax explicitly — in a written contract, not a chat thread — where the domain genuinely needs shared consistency.

Signal you chose wrong: a release runbook with the words “deploy A before B”. That sentence means you are paying both currencies at once: coupled systems and a coordination ritual.

Taught in: lesson 0033

Performance vs Maintainability

Forces: fast (performance) vs clear (maintainability).

Deciding questions: Is there a measured requirement, or a guess? Where is the bottleneck actually — profile first. Can the fast version hide behind a clean interface?

Default: clean until measured; then optimise the few hot paths and keep them behind a deep module.

Signal you chose wrong: an optimisation nobody can justify with a number, and a profile showing the real bottleneck somewhere else entirely — the cleverness bought complexity and no speed.

Taught in: lesson 0034

Evolution trade-offs

Stability vs Speed of Change

Forces: predictable, safe, hard to break (stable) vs quick to ship, quick to pivot (fast).

Deciding questions: What does a break cost — users, money, trust? What does waiting cost — market, feedback, debt? Can you buy speed safely with flags, a strangler, branch by abstraction, expand/contract?

Default: make change cheap and safe rather than choosing a side. The techniques that buy both are the master's toolkit.

Signal you chose wrong: a change that “had to” be one big deploy, and a rollback plan that was really a hope. Or the opposite: a process so heavy that a one-line fix takes a week and people route around it.

Taught in: lesson 0035

Isolation vs Integration

Forces: independent evolution (isolated) vs shared consistency and leverage (integrated).

Deciding questions: Do both sides mean the same thing by the same word, with the same rules? What must genuinely stay consistent across them? Who needs the shared schema — the domain, or a report?

Default: isolation at the seams the domain provides: bounded contexts, not blind modularisation.

Signal you chose wrong: a field named status2, or a migration that needs three teams in a meeting. One model is being bent to hold two meanings.

Taught in: lesson 0036

Correctness vs Delivery Speed

Forces: right (correct) vs now (fast).

Deciding questions: What does “wrong” cost — a rolled-back deploy, or a legal liability? Is the risky part cheaply testable? Which slice delivers value while keeping the irreversible part contained?

Default: correctness for irreversible or costly operations; speed for reversible ones, with tests as the safety net rather than the tax.

Signal you chose wrong: an incident that could not be fixed by a revert. That is the signature of an irreversible change shipped at reversible speed.

Taught in: lesson 0037

Short-Term Productivity vs Long-Term Maintainability

Forces: ship this week (productive) vs understand it next year (maintainable).

Deciding questions: Is this code touched once, or for years? Who maintains it — you, a team, or nobody? Is the debt recorded as debt, or disguised as done?

Default: you may borrow from the future — but record the loan. Debt that is invisible is never repaid.

Signal you chose wrong: the team estimates in multiples: “that used to be a day, now it's a week”. The interest is being paid; the loan was never written down.

Taught in: lesson 0038

Sources: Rich Hickey, Simple Made Easy; Kleppmann, Designing Data-Intensive Applications; Bass et al., Software Architecture in Practice; Fowler bliki (Technical Debt, Strangler Fig); Dan McKinley, Choose Boring Technology; ArjanCodes, Software Design Mastery.