← Back to principles

Team

Avoid Hasty Abstractions

Statement

Prefer duplication over the wrong abstraction. Let patterns emerge before you extract them.

What this means in practice

Abstractions should be earned through repeated, proven need — not created speculatively. Inspired by Kent C. Dodds' AHA (Avoid Hasty Abstractions) principle and Sandi Metz's guidance to "prefer duplication over the wrong abstraction", this principle sits between the extremes of DRY (Don't Repeat Yourself) and WET (Write Everything Twice).

  • Code that looks similar is not necessarily code that should be merged. Similarity is not the same as shared intent.

  • Duplication is tolerated until a clear, stable pattern has emerged across at least a few use cases.

  • When an abstraction is introduced, it should simplify the code for all current consumers — not just reduce line count.

  • Abstractions are optimised for change: they should be easy to modify or remove when requirements diverge.

  • If an existing abstraction no longer fits, it is better to inline it back and duplicate than to bend it with flags, parameters, and special cases.

  • The cost of the wrong abstraction compounds over time — every new consumer inherits its constraints and quirks.

Why this matters

The wrong abstraction is more damaging than duplicated code.

  • Premature abstractions couple unrelated concerns, making changes risky and expensive.

  • Shared code that tries to serve too many masters accumulates conditional logic, configuration options, and hidden side effects.

  • Developers avoid touching a bad abstraction for fear of breaking its consumers, so problems persist and workarounds multiply.

  • Duplicated code is easy to read, easy to change independently, and easy to delete — the wrong abstraction is none of these.

  • Teams that abstract too early often spend more time maintaining the abstraction than they saved by creating it.

Practices that meet this principle

  • Resist the urge to extract shared code after seeing only two similar instances. Wait for a third (or more) and confirm the pattern is stable.

  • When you do abstract, write the abstraction for the concrete cases you have today, not hypothetical future ones.

  • Keep abstractions small and focused — if an abstraction needs a mode parameter or feature flag to handle different callers, it may be doing too much.

  • Regularly review existing abstractions: if one has accumulated conditional paths or is only used in one place, consider inlining it.

  • When requirements for consumers diverge, inline the abstraction and let the code evolve independently rather than adding more options.

  • Use code review as a checkpoint — challenge new abstractions with "do we have enough evidence that this pattern is stable?"

  • Document the intent behind an abstraction so future developers understand when it applies and when it does not.

Validation

A project meets this principle when:

  • Abstractions are introduced based on observed, repeated patterns rather than anticipated reuse.

  • Shared code serves its consumers without requiring flags, modes, or special-case logic to accommodate different callers.

  • The team is comfortable with reasonable duplication and does not treat every instance of similar code as a problem to solve.

  • Existing abstractions are periodically reviewed and inlined or simplified when they no longer earn their complexity.

  • Code reviews include discussion of whether a proposed abstraction is justified by current evidence.

  • Developers can explain why an abstraction exists and which concrete cases it serves.

Potential blockers

  • A strong cultural bias towards DRY that treats any duplication as a code smell.

  • Linting rules or review norms that flag duplication without considering context.

  • Time pressure that discourages the effort of inlining a bad abstraction once it is established.

  • Large, entrenched abstractions where the cost of unwinding feels prohibitive.