The Broken Window Theory is the idea that visible disorder encourages more disorder. In software, the same thing can happen in a codebase: once messy patterns are established, future developers are more likely to copy them than clean them up. The broken window metaphor has been applied to software development in the past, most notably in the popular book The Pragmatic Programmer.

At the same time, we are taught not to over-engineer. Principles such as YAGNI (You ain’t gonna need it) and KISS (Keep it simple, stupid) encourage us to keep things simple, avoid premature abstractions and only introduce complexity when there is a real need for it. Both ideas are useful. The problem comes when a team applies YAGNI repeatedly without ever stopping to ask whether yesterday’s simple solution has now outgrown itself. What began as sensible restraint can gradually become technical debt, and once that debt becomes the established pattern, the Broken Window Theory starts to take over.

Consider building the backend for a website. The business requirements start off simple, so we keep our code simple and tight-knit too, housing view, business and database logic in one straightforward, easily accessible file. We avoid introducing extra layers or splitting the business logic across multiple modules, and skip heavier abstractions such as ORMs or clearer API and domain boundaries since we don’t need them just yet.

As time goes by, the product evolves. We add a little more to our simple program, satisfied that it is doing no more and no less than we currently require. We tell ourselves that when a change eventually pushes the code past the point where a domain layer, ORM or clearer API abstraction becomes worthwhile, the developer making that change will recognise it and spend the time introducing the abstraction before adding the new functionality.

In my experience, this rarely happens in practice. It is not usually that the developer fails to recognise the need for an abstraction; more often, they do not have the time or confidence to introduce one.

Another developer then comes along with the task of adding an entirely new piece of functionality. They notice that the established pattern is to keep everything in the same module, so they do the same. Another needs to add a more complex database query. They can see that an ORM might now be useful, but the rest of the codebase uses raw queries, so they add another one.

Yet another needs to expose a new endpoint. They find an existing endpoint with similar business logic, but that logic is too tightly coupled to the API layer to be reused. Rather than untangling it, they follow the established pattern and duplicate the logic, introducing even more coupling and duplication.

Each decision makes sense when viewed in isolation. The problem is that every developer is taking the existing architecture as a signal for how the next piece of functionality should be built. The longer this continues, the harder it becomes for any one developer to justify being the person who stops feature work and restructures everything. This is the Broken Window Theory in action: in a codebase with lots of active contributors, bad patterns beget bad patterns.

How can a team combat this, so that we can simultaneously avoid premature abstraction and broken windows leading to overdue abstraction? The worst thing to do here is to introduce a culture of blame, as that will just reduce trust and confidence even further. A more pragmatic course of action is to have processes and tooling that make it easier for developers to fix broken windows:

- Factor refactors into project estimates and deadlines, rather than expecting them to somehow fit around feature work.

- Periodically revisit earlier architectural decisions as the product grows. In particular, ask whether patterns that were deliberately kept simple are still appropriate, or whether they are now being copied simply because they already exist.

- Document not just which abstractions the codebase uses, but the conditions under which a team should introduce them. This gives developers more confidence to recognise when an earlier decision has reached the end of its useful life.

- Use AI tooling to flag cases where established patterns are being stretched beyond their original purpose. At a more advanced level, an architecture-focused agent could periodically examine the codebase as a whole and suggest areas where repeated local decisions are starting to create broader structural problems.

“We don’t need this yet” is often a good engineering decision. It just shouldn’t become a permanent one. YAGNI needs a feedback loop; without one, “we don’t need this yet” can quietly turn into “we should have done this six months ago.”