Design It Twice

Technique

Your first design idea is unlikely to be the best. But you'll never know unless you force yourself to consider alternatives. "Design It Twice" — from John Ousterhout's A Philosophy of Software Design — means generating at least two radically different approaches before committing to one.

Claude Code makes this practical: you can spin up parallel sub-agents that each explore a different design constraint, then compare the results side by side.

This technique is informed by Matt Pocock's design-an-interface skill.

The prompt

I need to design the interface for [describe your module/component/API].

## Requirements
- [What problem does it solve?]
- [Who are the callers? Other modules, external users, tests?]
- [What are the key operations?]
- [Any constraints? Performance, compatibility, existing patterns?]

## Process

Generate 3 radically different designs. Each must take a fundamentally different approach — not minor variations. To force divergence, apply these constraints:

- **Design A**: Minimize the interface. Aim for 1-3 methods/props max. Hide everything else.
- **Design B**: Maximize flexibility. Support the most use cases possible.
- **Design C**: Optimize for the most common case. Make the 80% path trivial, even if the 20% is harder.

For each design, show:
1. **Interface** — the types, methods, props, or API surface
2. **Usage example** — how a caller actually uses it
3. **What it hides** — complexity kept internal
4. **Trade-offs** — what you gain and what you give up

After showing all three, compare them on:
- Interface simplicity (fewer methods, simpler params)
- Depth (small interface hiding significant complexity = good)
- Ease of correct use vs ease of misuse
- How well it fits existing patterns in this codebase

Recommend one, but explain what you'd steal from the others.

The deep module principle

The best interfaces are deep: small surface area hiding significant complexity.

Deep module (good):          Shallow module (avoid):
┌──────────────┐             ┌────────────────────────────┐
│  Small API   │             │      Large API             │
├──────────────┤             ├────────────────────────────┤
│              │             │  Thin implementation       │
│  Complex     │             └────────────────────────────┘
│  internals   │
│              │
└──────────────┘

When comparing designs, ask: which one hides the most complexity behind the simplest interface?

When to use it

  • Before building a new module, component, or API
  • When you're about to write a function with 6+ parameters (there's a simpler interface hiding in there)
  • When two team members disagree on an approach (generate both, plus a third, then compare)
  • During architecture reviews

When NOT to use it

  • For trivial helpers or one-off utilities (just write it)
  • When the interface is already dictated by a framework or spec
  • When you're prototyping and speed matters more than design

Tips

  • The value is in the comparison, not any single design. Even if you pick Design A, insights from B and C will improve it.
  • "Radically different" means different in structure, not just naming. If all three designs have the same methods with different names, you haven't diverged enough.
  • The best final design often combines elements from multiple options. "Design A's interface with Design C's defaults" is a valid outcome.