Book Studio

Complexity Is Incremental

See how small design decisions accumulate into systems that are difficult to understand and change.

Complexity is anything in a software system that makes it harder to understand or modify. It is not the same as size. A large system can remain approachable when each task requires knowledge of only a small, well-defined area. A short program can be painfully complex when every function depends on hidden assumptions elsewhere.

The practical goal of design is therefore not to eliminate all difficulty. Some problems are inherently hard. The goal is to keep each developer from facing more difficulty than the current task requires.

Three symptoms

Complexity becomes visible through three recurring symptoms.

SymptomWhat the developer experiencesTypical cause
Change amplificationA simple feature requires edits in many placesOne decision is duplicated across modules
Cognitive loadToo much background knowledge is required before editingInterfaces expose implementation details
Unknown unknownsIt is unclear what must change or what might breakDependencies and conventions are hidden

Imagine adding a new delivery method to an online shop. If the method must be added to a checkout form, a price calculator, three validation switches, an email template, and a reporting query, the change is amplified. If those locations are documented, the work is tedious but knowable. If one location is discovered only after a production failure, the system also contains an unknown unknown.

Why small compromises matter

Complexity usually grows by increments. A hurried developer copies a condition instead of naming the policy. Another caller reads a private data shape because it is convenient. A third adds a configuration flag for one exceptional customer. Each choice looks harmless in isolation. Together they create a network of facts that must be remembered simultaneously.

This explains why cleanup cannot be postponed indefinitely. The later cost is not limited to removing the original shortcut. New code will already depend on it. A duplicated rule acquires more copies; a leaky representation gains more readers. The system teaches contributors to repeat its existing patterns, including its accidental ones.

A useful question

Before accepting a shortcut, ask which future changes will need to know that it exists. A local shortcut with no external dependency may be cheap. A shortcut that becomes part of several callers is design debt immediately.

Strategic programming

Tactical programming optimizes for finishing the current task as quickly as possible. Strategic programming spends a small, steady portion of the work improving the system's structure. This does not require a redesign project. It can mean choosing a clearer name, moving a rule to its owner, or simplifying an interface before adding another caller.

The distinction is about time horizon. A tactical choice may save ten minutes now and cost every later reader five minutes. A strategic choice pays a modest cost once so that the common path becomes cheaper. Teams that work strategically still deliver features; they treat design quality as part of delivering them.

Review the next change

When a change feels unexpectedly difficult, inspect the design before blaming the task:

  • Count how many places must change for one decision.
  • List the facts a developer must know before touching the code.
  • Look for behavior controlled by convention rather than an explicit interface.
  • Ask whether a small boundary change would make the next similar task cheaper.

Complexity has no single final fix. It is managed through thousands of decisions that either hide knowledge or spread it. The same incremental process that creates complexity can also reduce it.

On this page