When Every Change Hurts Everywhere
Two days.
That was the estimate for a ticket that, at first glance, genuinely looked like two days of work. A user was supposed to see a modal dialog. Their choice should be stored in a settings object. If they made no choice, the dialog should appear again after 30 days.
The developer who took the ticket was highly experienced, technically strong, and had an exceptionally good understanding of the existing system. If there was anyone in that codebase whom I would have trusted to spot hidden relationships early, it was him.
After roughly two weeks, he went on vacation. His status was:
“I’m done. Please let QA test it.”
What followed were roughly three more weeks of QA, fixes, and repeated testing. Over that period, we found around 40 defects or unexpected behaviors before the feature worked reliably.
This is a personal experience. The number 40 is not a typical metric for a Big Ball of Mud, five weeks is not a threshold, and a bad estimate does not prove bad architecture.
What made the ticket interesting was something else: after five weeks, the business requirement was still exactly the same as on day one. The dialog had not suddenly become more complicated, and the 30-day rule had not turned into a sophisticated algorithm. No new business complexity had appeared that could explain the effort. What had grown was the part of the system we had to understand.
The business change was small. Technically, it had no reliable boundary.
The dialog was not the problem
Section titled “The dialog was not the problem”The decisive defect was in how the settings were handled.
The same business-level settings concept was persisted in two different places. Both could save independently. As a result, the backend could contain two settings objects even though, from a business perspective, there was only one state.
When the data was loaded again, there was no rule that used a business identity to determine unambiguously which object was authoritative. Instead, the code simply used the first element returned by the collection:
index 0
There was no business contract for that ordering.
Whether the expected settings object or the other one happened to be at position zero was therefore not a property of the business process. It depended on a technical detail for which no business guarantee existed.
That alone did not explain every effect. The state was read and written in several places. Individual writes triggered further reactions. Timing mattered. Under certain execution sequences, race conditions appeared. A fix could remove one defect while exposing another state that had previously been hidden by the old ordering or by different timing.
The race conditions were not the architectural criterion. Race conditions can occur in very well-structured systems too. Likewise, multiple writers for the same state are, by themselves, still not proof of a Big Ball of Mud.
What mattered was that we became increasingly unable to answer a question that should have been almost trivial for this ticket:
Where does this change end?
Did it belong to the dialog? The settings service? A globally used state? The persistence model? The other places writing the same object? Every reader? The flows whose timing could change as a side effect of a fix?
The problem was not the dialog, but a state whose ownership, lifecycle, and persistence were not clearly bounded.

The business scope stays small. The technical impact surface can still grow.
One ticket does not prove a Big Ball of Mud
Section titled “One ticket does not prove a Big Ball of Mud”A single incident like this is not enough for a diagnosis.
Even in a well-structured system, a two-day estimate can turn out to be wrong. Requirements can be refined during implementation. An apparently simple state can have unexpected constraints. Concurrency can produce defects that only appear under specific timing. A developer can make a wrong assumption. QA can discover additional cases after implementation.
The observation becomes architecturally interesting only when it repeats as a pattern: seemingly local requirements regularly require knowledge about distant parts of the system. Responsibilities cannot be assigned clearly. Effects are difficult to bound before a change. Regression testing has to reach much farther than the business scope of the ticket would suggest.
One incident illustrates the mechanism. Repetition turns it into a property of the system.
The same mechanism appears in software-maintenance research. Change Impact Analysis asks which potential consequences a change may have and which parts of a system therefore need to be considered. That sounds abstract, but it describes the practical question behind almost every maintenance ticket: what else can this change affect?
As early as 1972, Parnas argued for modularization not primarily as an aesthetic division of source code, but as a way to improve comprehensibility and flexibility in the face of change. What matters is which design decisions can be hidden behind a module boundary instead of propagating through the system.
A Big Ball of Mud increasingly loses exactly this property. Not everywhere and not for every ticket, but often enough that the business and technical reach of a change no longer align reliably.
Three radii of a change
Section titled “Three radii of a change”For day-to-day development, a simple model helps. A change does not have only one size.
It has at least three different radii.
The business change radius
Section titled “The business change radius”The first radius describes what the requirement actually asks for.
In the example, that was manageable:
- show a dialog,
- store a choice,
- show the dialog again after 30 days if no choice was stored.
There was initially no more business complexity than that.
This radius says nothing about how many files will be changed. It describes the business reach: which rule, state, and behavior should change from the user’s point of view?
The technical change radius
Section titled “The technical change radius”The second radius describes which parts of the system actually have to be modified.
A local requirement may legitimately touch several components. A UI may need new state, an API may be extended, persistence may have to change, and tests may need to be added. A requirement touching several files or layers is therefore not an architecture problem by itself.
In a sustainable architecture, however, this technical radius can usually be explained.
This state belongs here. This component owns it. It is changed through this interface. It is persisted there. These consumers are affected. Beyond this boundary, the change ends.
The technical radius does not have to be identical to the business radius. But it should follow from it in a comprehensible way.
The comprehension and regression radius
Section titled “The comprehension and regression radius”The third radius is often the most expensive one in daily development.
It describes all the areas that do not necessarily have to be changed, but do have to be understood, checked, or tested before the change can be released responsibly.
A developer might commit only three files and still have to read twenty more.
Perhaps they have to find out who else writes the same state. Perhaps another flow uses the same service. Perhaps a relevant side-effect chain lives in a base class. Perhaps another team has to explain the implicit assumption behind an API. Perhaps QA needs regression tests across several user flows because nobody can confidently rule out that they are affected.
The patch can stay small while the change still becomes expensive.
The most expensive part of a change in a Big Ball of Mud can lie outside the patch itself.

The actual patch can stay small even when comprehension and regression have to cover large parts of the system.
The number of files is not the problem
Section titled “The number of files is not the problem”This also explains why the size of a diff says very little about the architectural quality of a change.
An API migration can modify a hundred files and still be exceptionally well controlled. Perhaps a clearly defined interface is being migrated mechanically. Every affected consumer is known. The compiler, tests, and static analysis reliably reveal where adaptations are required. The diff is large, but the impact surface is clear.
Conversely, three changed lines can be highly risky.
A boolean is set. A global service reacts to it. Another process reads the same state later. A base class contains an implicit side effect. Another place relies on a specific execution order.
The diff may show almost none of that.
The size of the diff is not the problem. The uncertainty about its effects is.
This distinction matters because otherwise we judge architecture by the wrong surface. Many changed files look like a large change. Few changed files look like a local change.
But the visible change is not the same thing as its actual impact surface.
Gall, Hajek, and Jazayeri showed in the late 1990s, using the release history of a large telecommunications system, that relevant “logical” dependencies can exist between modules that are not fully captured by obvious static relationships. Repeated co-change behavior can reveal relationships that source code or documentation alone do not make clear.
In day-to-day work, that means two places do not have to live in the same folder, import the same class, or be connected through an obvious interface for changes to them to be practically coupled.
A repository can look local and still resist local change.
Locality of Change
Section titled “Locality of Change”The previous diagnostic article in this series introduced Locality of Change as one indicator of whether business boundaries are still effective in the code.
Here, the economic consequence is what matters.
Locality of Change does not mean:
A business change may touch only one file.
Such a rule would be unrealistic and would create the wrong incentives in many architectures.
What it means is:
Business-local changes should have a technically bounded impact surface.
If I change the behavior of a setting, I may need to modify UI, application layer, state, persistence, and tests. That can be perfectly healthy. What matters is whether I know why those areas belong to the change and why other areas do not.
In that sense, architecture does not merely create structure. It also creates reliable exclusions.
It enables statements such as:
This state is changed only here.
This interface is the only path to persistence.
This slice owns this business decision.
These consumers depend on this contract.
Beyond this boundary, I do not need to keep searching for this change.
That last sentence is particularly valuable: a system is maintainable not only because relevant relationships can be found, but also because developers can reliably exclude large parts of the system as not relevant.
The less reliable those exclusions become, the larger the area that has to be considered just in case.
The business concern is local.
The system makes it global.
Change Amplification
Section titled “Change Amplification”John Ousterhout uses the term Change Amplification for a related phenomenon: an apparently simple change requires modifications in many different places. In his teaching material, the term appears alongside two other symptoms of complexity: high cognitive load and “unknown unknowns” — relevant information a developer does not initially know they even need.
For a Big Ball of Mud, the idea is useful as long as we do not turn it into a supposedly exact metric.
Change Amplification here does not mean counting files and declaring an architecture bad once some factor is exceeded.
Amplification can happen in several places.
A small business change can create additional analysis work. It can require technical changes in distant areas. It can expand the test scope. It can require additional review. It can force coordination with people who are not part of the business ticket at all. It can make the release appear riskier and therefore trigger additional safeguards.
The important point is not that all of these things happen on every change. The pattern becomes relevant when they happen repeatedly for changes that should be local. At that point, we are no longer looking at a few difficult tickets, but at a property of the system.
From a small ticket to a large impact surface
Section titled “From a small ticket to a large impact surface”The path usually starts quietly.
A requirement is local. During analysis, another state turns out to matter. That state is written in a second place. A further service therefore becomes relevant. That service is used by another flow. So that flow has to be checked as well. Regression grows. A reviewer identifies another dependency. QA adds additional scenarios.
A small business change gradually turns into a much larger assurance task.
Business-local change
→ larger analysis area
→ more technical touchpoints
→ larger potential impact surface
→ more regression
→ more review and coordination
→ greater release uncertainty
→ longer lead time
This chain is not a law of nature.
An additional dependency does not necessarily cause a defect. A broader regression test does not necessarily delay a release. A shared service is not automatically bad architecture.
The mechanism becomes problematic when technical boundaries lose their ability to stop the chain early.
Then it is no longer clear when enough analysis has been done.
And that brings us back to the same question:
Where does this change end?
Why QA kept finding new defects
Section titled “Why QA kept finding new defects”In our modal-dialog example, the implementation after two weeks was not worthless because the developer had done poor work.
The opposite was true.
He had already found and accounted for a significant part of the hidden relationships. Even so, QA repeatedly managed to produce additional failure states afterward.
The reason was not one bad patch, but the fact that the actual impact surface had not been reliably knowable before the change.
A fix might make sure a state was written correctly. That changed the timing of a later flow. Under that flow, a race condition became visible. The next correction could affect another writer or reader. That, in turn, exposed a state that the previous defect had hidden.
In simplified form, the pattern looked like this:
Fix → side effect → fix → another side effect.
Not every fix was wrong. The system simply made it difficult to predict where the consequences would end.
That is why the roughly 40 observed problems are less interesting to me today than the process behind them. A large number of defects can have many causes. The architecturally interesting part was that new portions of the same impact surface kept becoming visible after we thought we had already understood it.

The dialog did not keep getting larger. The system context relevant to it became visible.
Locality of Reasoning turns into working time
Section titled “Locality of Reasoning turns into working time”Closely related to Locality of Change is the previously introduced Locality of Reasoning.
If a business area can largely be understood from within itself, a developer needs only a bounded mental model for a local change. External dependencies still exist, of course. What matters is that contracts limit those dependencies and that their internals are allowed to remain largely irrelevant to the concrete change.
When this property is missing, more and more non-local behavior has to be understood for a local task.
The developer is no longer working only on the ticket. They are reconstructing the system as well.
Who owns this state? Who else writes it? What ordering is assumed? Is the service actually stateless? Which base class intervenes? Which other flows use the same object? Which tests encode real invariants, and which merely encode the current implementation?
Empirical research on program comprehension studies exactly these information needs in maintenance and change tasks. Sillito and colleagues, for example, analyzed the questions developers need to answer about an existing codebase while performing change tasks. The code edit itself is therefore only one part of maintenance work; enough relevant knowledge about the existing system must first be built up.
That is not fundamentally avoidable in legacy systems.
It becomes problematic when the mental model required for small changes regularly has to include a substantial part of the overall system.
A locally small ticket can then require a system-wide comprehension space.
Technical Debt creates work that is not in the feature
Section titled “Technical Debt creates work that is not in the feature”This also reconnects the argument to the Technical Debt discussion from the previous articles.
The business task in our example was:
Modal dialog and setting.
The actual work additionally included reconstructing state ownership, finding multiple writers, understanding persistence behavior, discovering an implicit ordering assumption, reproducing race conditions, analyzing regressions, and performing additional tests.
None of this made the modal dialog more valuable to the user.
The work was necessary so that we could deliver the desired business value reliably despite the existing structure.
This is where Technical Debt becomes economically interesting. Martini, Bosch, and Chaudron studied Architectural Technical Debt in several large companies and describe architectural decisions that can support short-term delivery while hindering future feature development over time.
Besker, Martini, and Bosch later examined more directly where developers lose time because of Technical Debt. In their longitudinal study and replication, developers reported substantial additional effort; additional analysis and testing were important sources of lost development time. The concrete percentages cannot simply be transferred to every organization, but the mechanism is relevant here: Technical Debt does not appear only as visible refactoring work. It also creates extra work during ordinary feature development.
That changes the question.
Not:
Why does this developer need five weeks for a dialog?
But:
Why does this system require five weeks of development and assurance work for a dialog?
That is more than a more polite wording. It changes the unit we are investigating.
The first question looks for the problem in the person or in the estimate. The second asks what in the system turns a small business requirement into a large technical work surface.
Estimates lose their foundation
Section titled “Estimates lose their foundation”This is also why effort estimates become difficult in such systems.
An estimate is always a statement about an expected change surface.
Someone estimating two days for the modal ticket may imagine the following work: add the dialog, use the existing settings service, implement the 30-day rule, write tests.
Under that assumption, two days can be entirely plausible.
Only during implementation does it become visible that the settings state has two writers, that the backend can contain two objects for the same business purpose, that reads rely on index 0, that the ordering has no business guarantee, that other readers react to the same state, and that timing changes behavior.
The known effort was not simply estimated badly. The assumed impact surface was wrong: the visible structure had suggested a local change that did not technically exist in that form.
A Big Ball of Mud does not merely make changes hard to predict. It makes the size of the change itself hard to predict.
That distinction matters. Of course, no developer can know every problem before starting a ticket. Estimates remain uncertain even in good architectures; software development is not a deterministic production line.
But sustainable boundaries reduce at least one form of uncertainty: they help constrain the relevant area before implementation begins.
When those boundaries are missing, you do not only estimate how much work will be required inside a known area. You also estimate how large that area is in the first place.
The Regression Surface
Section titled “The Regression Surface”For the third radius, I deliberately use a working term here: Regression Surface.
By it, I mean the part of the system that should reasonably be treated as potentially affected after a change.
This is not a hard mathematical surface and, in this article, not a standardized software metric.
The term simply helps formulate a practical question:
What do I need to verify after this change even though I may not have modified it?
In a well-bounded slice, this Regression Surface can remain relatively small. A change affects a business state with a known owner. Consumers interact through defined interfaces. Tests protect the relevant invariants. Other areas can be excluded with reasonable confidence.
When those boundaries are missing, the potential impact surface grows.
QA may have to test additional flows. A reviewer checks more services. A developer investigates historical special cases. Another team is asked whether its area depends on a jointly used state. Releases become more cautious because the list of things that are not affected becomes less trustworthy.
Change Impact Analysis addresses exactly this problem: it attempts to identify potential consequences of a change so that relevant parts of a system can be identified. The fact that research and practice have developed dedicated analysis techniques for this problem for decades already demonstrates that the visible patch and its possible effects are two different things.
Architecture helps determine how difficult that analysis is.
Where does the change end?
Section titled “Where does the change end?”That brings us to perhaps the simplest architecture question in this article.
Where does this change end?
A sustainable architecture should not provide a perfect answer, but it should provide one that is reliable enough.
This business state belongs here.
This component owns it.
It may be changed through this interface.
It is persisted through this path.
These consumers depend on it.
These tests protect its relevant invariants.
Beyond this point, the context no longer needs to be considered for this change.
This is not a demand for complete isolation. Real systems consist of dependencies. A change can deliberately affect several slices, services, or applications. Sometimes the business radius itself is large, and a large technical radius is entirely appropriate.
The problematic case is narrower:
The business concern is local. The system makes it global.
Then the three radii drift apart.
The business change radius stays small.
The technical change radius grows.
And the comprehension and regression radius may grow even more.

Architecture becomes especially valuable where it can reliably tell us which parts of the system no longer need to be considered.
And what about coding agents?
Section titled “And what about coding agents?”The previous article in this series showed that coding agents can handle large analysis surfaces today more efficiently than humans often can on their own.
That changes the effort, but not the underlying mechanism.
An agent can search faster for all writes to a settings object. It can follow readers, open services, reconstruct call chains, and run tests. This makes a large comprehension space easier to work with.
But it does not answer the architectural question of why that comprehension space became so large for a local change in the first place.
Locality of Change therefore remains relevant even as the tools change.
The dialog was never five weeks complicated
Section titled “The dialog was never five weeks complicated”Looking back at that ticket today, this is the part that matters to me.
The modal dialog was not five weeks complicated.
The business requirement was still just as small at the end as it had been at the beginning: show a dialog, store a choice, ask again after 30 days if nothing had been stored.
What became expensive was the path through a system in which the same business state had multiple writers, persistence did not produce one unambiguous truth, and even the selection of the authoritative settings object depended on an ordering that was not guaranteed.
The developer did not spend five weeks struggling to program a dialog.
We spent several weeks finding out what that dialog actually touched in the rest of the system and making those effects safe enough to release.
That is why a Big Ball of Mud should not be identified by the idea that every change must be large. Many changes remain small. Some work immediately. Some areas can be surprisingly stable.
The problem is the lack of reliability. In a sustainable architecture, a local change normally also has a technically bounded impact surface. In a Big Ball of Mud, the business change radius, technical change radius, and required comprehension space can drift increasingly far apart.
That is why the most interesting question after a ticket like this is not:
How many files changed?
But:
How far did we have to understand and verify the system in order to make this local change responsibly?
And more generally:
Where can I stop thinking about this change with reasonable confidence?
If the answer is regularly “nowhere,” eventually every change really does hurt everywhere.
Sources and further research
Section titled “Sources and further research”- David L. Parnas: On the Criteria To Be Used in Decomposing Systems into Modules. Communications of the ACM 15(12), 1972, pp. 1053–1058. DOI:
10.1145/361598.361623. The paper motivates modularization in part through comprehensibility and flexibility under change, and through hiding changeable design decisions behind module boundaries. - Harald Gall, Karin Hajek, Mehdi Jazayeri: Detection of Logical Coupling Based on Product Release History. ICSM 1998, pp. 190–198. DOI:
10.1109/ICSM.1998.738508. The authors analyze co-change patterns in the release history of a large telecommunications system and show how such patterns can reveal logical dependencies beyond obvious static coupling. - Cleidson R. B. de Souza, David F. Redmiles: An Empirical Study of Software Developers’ Management of Dependencies and Changes. ICSE 2008, pp. 241–250. DOI:
10.1145/1368088.1368122. The paper frames Change Impact Analysis as identifying potential consequences of a change and studies how developers manage dependencies and changes in practice. - Jonathan Sillito, Gail C. Murphy, Kris De Volder: Questions Programmers Ask During Software Evolution Tasks. SIGSOFT FSE 2006, pp. 23–34. DOI:
10.1145/1181775.1181779. The study examines the information developers need about a codebase while carrying out concrete change tasks. - Antonio Martini, Jan Bosch, Michel Chaudron: Investigating Architectural Technical Debt Accumulation and Refactoring over Time: A Multiple-Case Study. Information and Software Technology 67, 2015, pp. 237–253. DOI:
10.1016/j.infsof.2015.07.005. The study spans seven sites in five large companies and examines the accumulation and long-term consequences of Architectural Technical Debt. - Terese Besker, Antonio Martini, Jan Bosch: Software Developer Productivity Loss Due to Technical Debt – A Replication and Extension Study Examining Developers’ Development Work. Journal of Systems and Software 156, 2019, pp. 41–61. DOI:
10.1016/j.jss.2019.06.004. The study examines additional development work caused by Technical Debt and identifies additional analysis and testing as important sources of lost development time. - John Ousterhout: The Nature of Complexity. Stanford CS 190, Lecture Notes, 2018. Ousterhout uses “Change Amplification” for the case in which a simple change requires modifications in many places and places it alongside Cognitive Load and “Unknown Unknowns”.