Simplicity vs Flexibility
Flexibility has a carrying cost that is paid every day, while its benefit arrives only if you guessed the right axis of change. Learn when to buy it.
Every architect has been tempted by the generic solution: a plugin system, a configurable rules engine, an abstraction that can support any database. Flexibility feels like insurance. But insurance has premiums, and in software those are paid in complexity, onboarding time and bugs, whether or not the claim ever comes.
The hidden price of flexibility
Flexibility is not free capacity; it is a bet on a particular future. Each extension point adds:
- More code paths to test and reason about
- Indirection that makes debugging slower
- Configuration that must be documented and validated
- A contract you must keep stable for whoever uses the extension point
If the future you predicted does not arrive, you carry all of this for nothing. If a different future arrives, your flexibility is often in the wrong place, and you pay twice: once to remove it and once to build what was actually needed.
A concrete example
Suppose you need to send notifications by email. The flexible version:
NotificationEngine
-> ChannelRegistry
-> ChannelProvider (interface)
-> TemplateResolver
-> DeliveryStrategy (pluggable)
The simple version:
sendOrderConfirmation(order) // calls the email provider directly
The simple version is thirty lines. The flexible one is a thousand and has one implementation. When SMS arrives six months later, the simple function is extended in an afternoon, and now you know what SMS actually requires: opt-in rules, character limits, a different provider. The abstraction you would have guessed likely would not have fit.
Flexibility where change is certain
None of this means abstraction is bad. The goal is to place flexibility on axes where change is likely and costly, and keep everything else direct. Use evidence:
| Signal | Interpretation | |---|---| | Three real variants exist today | Abstract them | | Two variants exist, a third is planned and funded | Consider abstracting | | One variant, others are hypothetical | Keep it concrete | | Vendor risk is high and switching cost is big | Isolate behind a thin adapter | | Behavior changes weekly by non-engineers | Make it data driven |
The rule of three is a useful heuristic: tolerate duplication until the third instance, because by then the shared shape is visible instead of imagined.
Prefer cheap ways to stay changeable
You can keep options open without building frameworks:
- Keep modules small and dependencies explicit. Code that is easy to delete is easy to replace.
- Separate policy from mechanism only when policy changes. A discount rule that marketing edits weekly deserves configuration. A tax rounding rule does not.
- Use narrow interfaces. A function taking exactly what it needs is more adaptable than one taking a giant context object.
- Write tests at behavior boundaries. Good tests make refactoring safe, which is the real source of flexibility.
Simplicity itself is a form of flexibility. A system you can fully understand can be modified confidently, while an over-abstracted one is modified with fear.
Recognising the over-flexible system
Warning signs include classes with a single implementation and a name ending in Manager or Factory, configuration files longer than the code they configure, and questions like "which layer should this live in?" that nobody can answer. Another is the generic data model: a table with key, type and value columns that can store anything and therefore enforces nothing.
The reverse failure exists too. If every change requires editing twelve files because a vendor type leaked everywhere, you lacked a boundary. The fix is a targeted seam, not a framework.
Key takeaways
- Flexibility is a speculative investment with ongoing carrying costs.
- Abstract on evidence: wait for real variants, then generalise from them.
- Concentrate flexibility on axes of likely, costly change such as vendors and frequently edited policy.
- Small modules, narrow interfaces and good tests keep a system changeable without frameworks.
- Simple code that you understand is usually the most flexible asset you have.