In this lesson you'll learn
- The difference between essential and accidental complexity
- Why complexity, not feature count, is what actually slows teams down
- How a model that drifts from the business produces a "big ball of mud"
- Where DDD targets its effort to attack the costly kind of complexity
Ask any engineer why a project is late and you'll rarely hear "we ran out of keystrokes." You'll hear that the code is tangled, that nobody fully understands the order flow, that one change broke three unrelated things. Those are symptoms of complexity — and complexity has a price that compounds.
Two kinds of complexity
Fred Brooks drew the line that still defines our field. Some complexity is essential: it is inherent in the problem itself. A tax calculation is genuinely intricate; a shipping network genuinely has many moving parts. You cannot wish it away. The other kind is accidental: complexity we introduce through poor structure, unclear names, leaky abstractions, and models that no longer match reality.
Essential complexity — the problem's own difficulty
In ShopSphere, the rule "a customer may only return an item within 30 days, unless it was a final-sale item, unless they are a Premium member" is essential. The business really is that nuanced. DDD does not remove this — it gives you a place to put it where it stays visible and correct.
Accidental complexity — the difficulty we add
When that same return rule is smeared across a controller, a stored procedure, three if blocks, and a boolean named flag2, you have manufactured accidental complexity. The business didn't get harder; your representation of it did.
How the cost compounds
Accidental complexity is dangerous because it is not linear. Each unclear abstraction makes the next change harder to reason about, which invites another shortcut, which adds more unclarity. This feedback loop is how a clean codebase becomes what Brian Foster and Joseph Yoder famously named the Big Ball of Mud: a system "haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire" — held together only by the few people who remember how it works.
| Symptom you feel | Underlying cause | What it costs |
|---|---|---|
| A small change touches many files | Logic is scattered, not localized in a model | Slow delivery, high regression risk |
"What does status = 3 mean?" | Code doesn't speak the business language | Onboarding pain, recurring bugs |
| Fixed bugs reappear | No single owner of the invariant | Eroding trust, firefighting |
| Only one person understands a module | Knowledge lives in heads, not the model | Bus-factor risk, bottlenecks |
Where the cost actually lives
Studies of software economics keep landing on the same number: the large majority of a system's lifetime cost is maintenance, not initial construction. We spend most of our money understanding existing code so we can change it safely. That reframes the whole game.
If reading and understanding dominate cost, then code that reads like the business — code whose names, types, and structure match how an expert describes the work — is not a nicety. It is the highest-leverage investment you can make. That is precisely the leverage DDD pursues.
Consider the same rule expressed two ways. First, the accidental-complexity version:
OrderService.java — business meaning buried in primitives
// What is 3? What is 30? Why this customer field?
public boolean canReturn(Order o, int days) {
if (o.getStatus() == 3 && days <= 30) {
if (o.getFlag2() == false || o.getCustomer().getTier() == 2) {
return true;
}
}
return false;
}Now the version where the model carries the language and the rule lives with the data it governs:
Order.java — the rule reads like the policy
public boolean isReturnable(LocalDate today, ReturnPolicy policy) {
if (status != OrderStatus.DELIVERED) {
return false;
}
boolean withinWindow = deliveredOn.plusDays(policy.windowDays())
.isAfter(today);
return withinWindow && (!finalSale || customer.isPremium());
}What DDD does about it
DDD is not a framework you install; it is a discipline aimed squarely at accidental complexity. It does this on two fronts. Strategically, it carves a sprawling problem into bounded contexts so no single model has to be everything to everyone. Tactically, it gives each part a model whose code speaks the domain's language and protects its own rules. The payoff is the flat curve in the diagram above: the tenth feature costs roughly what the first one did.
Key takeaways
- Essential complexity is inherent in the problem; accidental complexity is what we add through poor modeling.
- Accidental complexity compounds non-linearly and is how clean code decays into a big ball of mud.
- Most of a system's cost is maintenance — i.e. understanding code in order to change it safely.
- DDD targets accidental complexity by making code mirror the business language and localize its rules.