Why Simple Changes Rarely Stay Simple

How software systems become resistant to simple change

Why Simple Changes Rarely Stay Simple
Image Credit: Nutthaseth Vanchaichana

One of the most common experiences in software development is also one of the least questioned: a seemingly small, well-understood change that ends up taking weeks or even months to deliver. The requirements are clear and the domain is familiar. Yet, despite the fact that no new technology is involved, the effort required is growing disproportionately. What initially looks like a minor adjustment suddenly touches multiple services, multiple teams, multiple approval steps, and multiple release cycles. Over time, this stops being surprising and becomes accepted as “how things are”.

What makes this phenomenon interesting is not that it happens, but that it happens so consistently, even in organizations with competent engineers, modern technology stacks, and well-established processes. If the problem were merely a lack of skill or outdated tooling, the pattern would be less universal. Instead, it appears across companies, domains, and architectures, regardless of whether they follow monoliths, microservices, or something in between. That consistency suggests that the root cause lies elsewhere.

The Explanations We Reach for First

The explanations typically offered are familiar.

  • The domain is complex.
  • The system has grown organically.
  • There is legacy code that cannot be touched easily.
  • Regulatory constraints require extra care.
  • External dependencies slow things down.

None of these explanations are wrong, but they rarely explain why even small changes behave the same way as large ones. They describe the environment, not the mechanics that turn simplicity into friction. This distinction becomes important when you start viewing systems not as static structures, but as the result of decisions made over time.

What stands out in many of these situations is that the people involved are rarely the problem. Teams understand the domain, developers are experienced, and architects are capable of articulating the system’s design. CI pipelines run reliably, observability is in place, and documentation exists. From the outside, the setup looks mature. Yet delivery slows down, estimates become defensive, and predictability erodes. When competent people consistently produce disappointing outcomes, it is usually a sign that the system they operate in behaves in ways that no individual can easily override. This is a theme that shows up whenever responsibility and execution drift apart.

Software systems do not resist change by accident. Over time, they develop a form of inertia that is the result of countless decisions made under local constraints. Each decision may have been reasonable in isolation, but together they create a structure that increasingly resists modification. The important part is that this resistance is not abstract or cultural; it is mechanical. It is embedded in how code is structured, how responsibilities are divided, and how decisions flow through the organization, often long after their original context has disappeared.

When One Decision Exists in Many Places

A recurring pattern in such systems is the fragmentation of domain logic. A single business rule, conceptually simple and well understood, ends up being implemented in multiple places. Sometimes this is done intentionally in the name of separation of concerns or reuse. Sometimes it happens gradually as systems evolve. The effect is always the same: a single conceptual change now requires coordinated updates across several technical boundaries. Each boundary introduces its own constraints, owners, and release rhythms. What was once a straightforward decision becomes a negotiation across contexts, and the system starts to behave less like a cohesive whole and more like a collection of loosely aligned interpretations of the same idea.

Closely related to this is the issue of responsibility. Ownership is often clearly defined on paper. Services have owners, repositories have maintainers, and teams have scopes. In practice, decision authority is frequently distributed differently. One team may own the code, another the domain logic, and yet another the release process. This separation is often introduced to reduce risk or increase control, but it has the side effect of making changes harder to execute. Shared responsibility and ambiguous authority are two sides of the same coin. This pattern is reminiscent of what happens when systems prioritize safety over change.

Abstraction as a Way to Avoid Commitment

Abstraction plays a central role in how complexity stabilizes itself. Abstractions are usually introduced with the promise of flexibility and safety. They aim to decouple concerns, enable reuse, or prepare for future variation. Over time, many abstractions stop serving change and start serving reassurance. They become a way to postpone commitment, to keep options open that will never be exercised, or to protect teams from making explicit decisions. As behavior accumulates, these abstractions become harder to remove, and every change must pass through them. What was intended to simplify ends up preserving complexity. This is a phenomenon that often looks like architectural rigor while functionally acting as a brake.

Distribution is another area where intention and outcome often diverge. Many modern systems are split into services with clearly defined interfaces, suggesting a high degree of independence. In practice, these services frequently move together. Shared data models, synchronous call chains, and coordinated releases create implicit coupling that is not visible in architecture diagrams. The system appears modular, but behaves as a single, tightly bound unit when changes are introduced. Distribution without true autonomy does not reduce complexity; it merely relocates it, often making causal relationships harder to see.

As structural friction increases, organizations often respond by adding tooling. More pipelines, more checks, more dashboards, and more coordination mechanisms are introduced to regain control. These tools are not inherently problematic; many of them are necessary in complex environments. However, they often act as compensatory mechanisms rather than solutions. They help manage the symptoms of structural issues without addressing their causes. The system becomes more observable and more controlled, but not necessarily easier to change. This is a distinction that matters if effectiveness, not compliance, is the goal.

At a certain point, complexity becomes self-reinforcing. Every change feels risky, which justifies additional safeguards. These safeguards add structure, which increases friction, making future changes even riskier. Over time, the organization adapts its behavior to this reality. Estimates grow, timelines stretch, and innovation slows. This adaptation is often seen as a sign of maturity, but it is equally often a response to structural resistance and a state of equilibrium that favors stability over change.

Architecture discussions tend to intensify in such environments. New diagrams are created, target states are defined, and principles are refined. While these activities can be valuable, they often remain descriptive rather than diagnostic. They explain how the system is structured, but not why it behaves the way it does. Without examining the mechanics of change, architecture remains a reflection of existing decisions rather than a challenge to them, and structure becomes something to be defended rather than questioned.

Looking at Change Instead of Structure

What is frequently missing is a simple but uncomfortable perspective: observing what actually happens when a change is made. Which files change together? Which services are involved? Which teams need to align? Which approvals are triggered? These questions are concrete and observable. They do not require theory or frameworks. They also tend to reveal patterns that are difficult to ignore once seen, because they expose how decisions are enforced in practice rather than how they are described.

Tracing real changes through a system makes priorities visible. It shows what is protected, what is duplicated, and what is centralized. These patterns are rarely the result of a single design decision. They emerge incrementally as the system evolves. Each step makes sense locally, but together they create a structure that resists modification. Over time, explanations replace decisions. The organization becomes adept at explaining why things are the way they are, but less willing to change them. This represents a shift from action to justification.

Large initiatives can hide these issues. Small changes cannot. They move through existing structures and expose every friction point along the way. This is why small changes are such powerful diagnostics. They reveal the true cost of the system’s structure, not in theory, but in practice, and they do so without the noise that often surrounds large transformation efforts.

None of this is about blaming individuals or teams. These dynamics arise in well-intentioned organizations with capable people. They are the result of accumulated decisions that were never revisited once their original context disappeared. Once embedded, they operate independently of intent, shaping outcomes even when everyone involved wants improvement.

If you strip away the narratives and just look at what remains, the picture becomes clearer. How many places need to change? How many parties need to agree? How much coordination is required? These factors define the real cost of change, and they are measurable. Over time, organizations that cannot change easily do not stop delivering altogether. They slow down, become selective, and defer decisions. They lose flexibility, not because they lack ideas, but because implementing them becomes costly.

What Simple Changes Reveal

At some point, a different question becomes unavoidable. Not how to make changes easier, but what structure makes them so hard in the first place. That question rarely has a comfortable answer, because it points back to decisions that once felt necessary and now feel immutable.

If simple changes rarely stay simple, it is not because software is inherently complex. It is because structure accumulates faster than intent, and because decisions, once distributed and protected, are rarely revisited. Seeing complexity not as a technical challenge but as a structural consequence changes how problems are framed. It shifts the focus from finding more solutions to understanding the constraints that quietly shape every outcome, and this shift is often where real change begins.

Cheers!

This article was originally published here.

Subscribe to Rico Fritzsche

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe