Incidental vs. Accidental Complexity

Not all complexity is the same. Incidental complexity is the cost of the problem itself -- you cannot eliminate it. Accidental complexity is the cost of your implementation choices -- and you absolutely can.

June 7, 20264 min read1 / 2

There are two kinds of complexity in every codebase. Only one of them is your fault.

I kept running into this distinction without having a name for it. Some complexity felt necessary -- the rules the product demanded. Some felt like something I had built on top of those rules. Both were real. Only one was reducible.

Incidental Complexity

Incidental complexity comes from the problem domain itself. It is irreducible.

A reservation system has rules that will exist regardless of how you build it:

  • A booking cannot overlap another booking
  • Payment must succeed before a reservation is confirmed
  • Cancellations are only allowed up to a deadline

These rules exist because the product requires them. Switch from React to Vue. Switch from TypeScript to Python. Rewrite the whole thing from scratch.

The rules stay exactly the same.

This is complexity you cannot eliminate. You can only understand it clearly.

Accidental Complexity

Accidental complexity is the complexity you introduce through implementation choices. It is self-inflicted and reducible.

Here is a reservation form that mixes both types:

TSX
function ReservationForm() { const [dateRange, setDateRange] = useState<DateRange | null>(null); const [isChecking, setIsChecking] = useState(false); const [isAvailable, setIsAvailable] = useState<boolean | null>(null); const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); const [hasError, setHasError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); useEffect(() => { if (dateRange) setIsChecking(true); }, [dateRange]); useEffect(() => { if (!isChecking) return; checkAvailability(dateRange).then(result => { setIsAvailable(result); setIsChecking(false); }); }, [isChecking]); // ... }

How many of these seven state variables come from the reservation rules? The date range, maybe. But isChecking, isSubmitting, isSubmitted, hasError, errorMessage -- all of these are decisions about how to represent a flow that has exactly two dimensions: what step the user is on, and whether something went wrong.

The product did not ask for seven state variables. You chose that shape.

The cascading useEffect chains from the intro post are accidental complexity. The redundant state we covered in the anti-patterns chapter is accidental complexity. The four boolean flags that can all be true simultaneously -- accidental.

None of these are requirements. They are the cost of specific choices made while writing code.

Why the Distinction Matters

When a codebase becomes hard to work in, the two types of complexity have been layered on top of each other. You can no longer tell what is a genuine business rule and what is a byproduct of an implementation decision made two years ago.

The goal of modeling is to separate them. Write down the actual rules first. That is the incidental complexity.

Everything you add in code that is not required by those rules -- that is the accidental complexity you are introducing.

Once you can see the boundary, you can ask a precise question when something breaks: is the rule wrong, or did the code diverge from the rule?

Without that separation, debugging means guessing which layer the problem lives in.

What Modeling Actually Means Here

Modeling does not mean UML diagrams or formal specification. It means writing down the real flows of your application before you write code -- the entities, the actor flows, the valid states and transitions -- in whatever format helps you think clearly.

A rough text description in a flows.md file inside your repository is more useful than a formal diagram nobody maintains. This file becomes context for your teammates and for the AI tools that read your codebase.

Two-column comparison: left column shows incidental complexity items (booking cannot overlap, payment must succeed, cancellations have deadlines) labeled as irreducible and the same in any stack; right column shows accidental complexity items (8 useState calls, chained useEffects, three booleans for loading/error/success) labeled as reducible and a result of implementation choices ExpandTwo-column comparison: left column shows incidental complexity items (booking cannot overlap, payment must succeed, cancellations have deadlines) labeled as irreducible and the same in any stack; right column shows accidental complexity items (8 useState calls, chained useEffects, three booleans for loading/error/success) labeled as reducible and a result of implementation choices

The less accidental complexity you introduce, the more clearly the incidental complexity shows through. And once you can see it clearly, you can model it.

The next post covers the three specific diagram types that each expose a different dimension of that incidental complexity: Modeling Application State with Diagrams.

The Essentials

  1. Incidental complexity comes from the problem itself. It is irreducible. It stays the same no matter what stack or tools you use. Your job is to understand it clearly, not eliminate it.
  2. Accidental complexity comes from your implementation choices. Cascading effects, redundant state, impossible boolean combinations -- these are all reducible. They are not requirements of the problem domain.
  3. Modeling exposes the boundary between the two. Writing down flows and rules before writing code draws a clear line between what you must handle and what you are about to introduce yourself.

Further Reading and Watching

  • Thinking in React -- React Docs: A walkthrough of starting from the problem -- the component hierarchy, the data model -- before writing code. The same discipline as separating incidental from accidental complexity.
  • Simple Made Easy -- Rich Hickey, Strange Loop 2011: A foundational talk about the difference between simple and complex as objective properties of a system. The argument maps directly onto the incidental vs accidental distinction. Note: verify this YouTube link before publishing.