--- name: design-it-twice description: "Invoke before committing to a significant interface: new module, service boundary, public API, or library contract. Also when someone says the design is obvious or a review only tweaks names. Sketch two or more radically different designs and compare before implementing." --- # Skill: Design It Twice **Source**: *A Philosophy of Software Design* by John Ousterhout (2018), Chapter 11\ **Skill ID**: design-it-twice\ **Category**: Interface Design / Decision-Making ______________________________________________________________________ ## R — Reading (Original Source) > "If you are a good engineer, you may be wondering: why do I need to consider a second design, when I already know the right answer? ... The design-it-twice principle is particularly important for interfaces. The interface for a module is its most important design decision, because it determines how the module will be used and how it interacts with the rest of the system. Once an interface has been established and used in other modules, it is difficult to change. Thus, it is worth spending extra time up front to get the interface right. I have been amazed by how often a design that is clearly inferior at the outset turns out to be the better choice after all." ## — John Ousterhout, a Philosophy of Software Design, Chapter 11 ______________________________________________________________________ ## I — Interpretation Design-it-twice is not a hedge against uncertainty — it is a correction for a systematic cognitive failure. The first design is almost never optimal, not because the designer lacks skill, but because a single design cannot reveal its own trade-offs. Trade-offs only become visible when there is something to compare against. The deeper insight is psychological. The principle's primary target is the experienced engineer, not the novice. Novices generate alternatives naturally because they are uncertain. Experts resist alternatives because experience produces confidence, and confidence produces the first-idea trap: the belief that the right answer is already known before comparison has occurred. Ousterhout addresses this directly, which is unusual — most design advice assumes the reader needs encouragement to think harder. This chapter assumes the reader is already smart, and explains why that makes them more susceptible, not less. The comparison process is generative. Examining two designs forces articulation of what each design is actually optimized for, which use cases it handles gracefully and which it handles poorly. Elements from the rejected design frequently survive into the final design. This is not a side effect — it is part of the value. ______________________________________________________________________ ## A1 — Past Application (Author's Cases) Ousterhout uses interface design as the canonical domain throughout Chapter 11. The primary example is module interface design: given a module you are about to write, generate two fundamentally different interfaces — different abstractions, different granularity, different caller experience — before writing any implementation. The domain is intentionally narrow. He is not asking for two implementations. He is asking for two interface contracts: different method signatures, different responsibility splits, different levels of abstraction exposed to callers. Implementation is deferred. The comparison is about what callers see and what callers must do, not about how the code works internally. The behavioral observation about elements of the rejected design appearing in the final design is explicit in the chapter. Comparison reveals that each design got something right that the other did not. The final choice is rarely a clean win for either original candidate. The resistance evidence is also direct. Ousterhout names smart, experienced engineers specifically as the population most likely to skip this step. The reasoning: if you have built many systems, your first design is often competent — competent enough to obscure the fact that a different framing would have been better. The cost of that competence is reduced willingness to question the frame. ______________________________________________________________________ ## A2 — Future Trigger ★ Invoke design-it-twice in any of these situations: - **Any significant interface decision**: a new module, service boundary, public API, or library contract — before writing the first line of implementation. - **Before committing a module boundary**: when deciding what belongs in this module versus that one, or what callers must own versus what the module owns. - **When a senior developer says "obviously we should do X"**: the word "obviously" is a signal that comparison has been skipped. The confidence is the problem. - **When a design review produces only minor refinements**: if feedback is all about naming, parameter order, or error messages — but no one has proposed a structurally different design — the review is operating on a single frame. - **When someone proposes an API design in the first 10 minutes of a discussion**: early proposals are generated by the first-idea trap. If the room converges on it without generating an alternative, the convergence is premature. - **When refactoring an existing module**: before committing to a refactoring approach, generate a second structurally different refactoring path and compare. The first refactoring idea is usually a local improvement; the second often reveals a better restructuring. ______________________________________________________________________ ## E — Execution (Steps) **Step 1 — Write down Design A before touching code.**\ Document the interface only: method signatures and a one-sentence description of each method's responsibility. Do not write implementation. The act of writing it down is required — unwritten designs are not designs, they are intentions. **Step 2 — Generate Design B as a genuinely different design.**\ Different means different abstraction level, different responsibility split, different granularity, or different caller burden — not renamed parameters or reordered arguments. If Design B feels like a variant of Design A, it is not different enough. Common moves: collapse multiple methods into one, split one method into two, push responsibility from the module to the caller (or the reverse), change the level of abstraction entirely. **Step 3 — Compare trade-offs per use case.**\ For each use case you know about, ask: what does the caller have to do with Design A vs. Design B? Which design handles this case more cleanly? List this explicitly. Trade-offs that are not written down are not trade-offs — they are impressions. **Step 4 — Choose the better design and note what to salvage from the rejected one.**\ The choice rarely ends the process. Identify specific elements of the rejected design that were superior and incorporate them into the chosen design. Document why the final design is what it is. **Step 5 — If you cannot generate Design B within 15 minutes, stop.**\ Inability to generate an alternative is a signal, not a time management problem. It means the problem space is not yet understood well enough to make any design decision. The right response is to gather more information about use cases, not to proceed with Design A by default. ______________________________________________________________________ ## B — Boundary (When Not to Apply) **Implementation details, not interfaces.**\ Design-it-twice is about interface contracts — what callers see and use. It does not apply to variable names, private function signatures, algorithm choice inside a function, or minor internal refactors. The overhead is not justified for decisions that are easy to reverse and affect only one scope. **Use cases must be known first.**\ Comparing two designs requires knowing what use cases they must handle. Without that context, the comparison is aesthetic, not analytical. If you do not yet know who will call this module and what they will need to do, design-it-twice cannot be applied correctly — the prerequisite is understanding the caller's needs, not generating alternatives in a vacuum. **Throwaway code and single-caller utilities.**\ Code that will be discarded after a single use, internal utilities with exactly one caller, and scripts with no re-use expectations do not warrant the overhead. The principle is calibrated to decisions that are hard to reverse and affect multiple callers over time. When the interface will never be seen by another person or used in a second context, the comparison process is not cost-effective. **Do not apply it as a ritual.**\ If design-it-twice becomes a mandatory bureaucratic step rather than a genuine attempt to find a better design, it will produce Design B as a strawman — constructed to lose. The principle requires intellectual honesty. A deliberately weak alternative does not satisfy the intent. ______________________________________________________________________ ## Related Skills - **Strategic vs. Tactical Programming (`design-stamina-strategic-investment`)** — *enabled-by* → Tactical programmers skip design-it-twice because it produces no immediate feature. The strategic 10–20% investment budget specifically covers design exploration like this. - **Write Comments First** — *precedes* → Design-it-twice selects the interface structure; comments-first then verifies the chosen design is expressible simply. Running comments-first on both candidate designs is a particularly effective combination.