Keep zooming out
Teams can end up encoding service and organisational complexity in the codebase they control.
I've seen capable teams spend months improving something and make surprisingly little difference to how quickly they can deliver. The work itself was thoughtful. The problem was where they applied it. They took complexity that belonged to a wider system and tried to solve it inside the codebase they owned.
I picture a triangle. Draw one around the codebase a team owns and, from close up, it looks like the whole shape. There are boundaries, dependencies, and places where change gets stuck. Zoom out and the sides keep extending: that codebase connects to other services, those services belong to other teams, and those teams have their own dependencies and decisions. The team may not even have a clear view of that wider triangle. So it tries to fit the whole problem into the small one it can see and control.
The same problem at a larger scale
In a monolith, the codebase could show you much of the architecture. If a feature was hard to change, the cause might be a tangled module or a shared database table. Improving the code could make delivery easier in a fairly direct way.
When the application is spread across services, repositories, and cloud accounts, the code inside any one service is only one part of the architecture. A team can maintain its own code beautifully and still wait weeks for another team to update a contract, make a decision, or release its part of a feature. It may respond by adding options, adapters, and workarounds to its own service, because changing the wider arrangement feels out of reach.
So when we say we want maintainable software, I think we need to ask a larger question: how easily can the organisation change the system as a whole?
That question doesn't always lead to a bigger solution. If tangled code inside a monolith is slowing changes, improving that code is working at the right scale. If releases repeatedly wait on another service's contract, adding another adapter inside our service may only hide the issue. The useful change may be at the contract. If several teams cannot complete a feature without negotiating every step, the complexity may sit in ownership or planning. Keep zooming out until you can name which triangle contains the problem. Then work at that scale.
Why the complexity ends up in our code
The code we own is where we can act. We can open it in an editor, change it without permission from another team, and point to the result. We may also lack a map of the services and decisions beyond it. In my deliberately exaggerated version of this, frameworks and libraries have already extracted 95% of what we'd otherwise build, and we spend months turning another 4% into a library for ourselves and perhaps two other teams.
That library might help. It might also add versioning, release coordination, and another dependency to every change. The local code gets tidier while the wider system gets harder to move through.
A backend for frontend can follow the same pattern. A team needs data for its UI, but may not know that an API already exists elsewhere in the organisation. Building a service it owns feels like a direct solution. Sometimes frontend-specific composition genuinely belongs there. But if the new backend duplicates an existing capability, the team has created another service and contract to maintain because it could not see or reach the wider solution.
A shared design system is a good example. In my post about using Storybook as a solo developer, I described how useful it became to inspect components and awkward states in one growing dashboard. Those stories still need maintenance. Sharing the components across teams would be a much larger commitment.
If one team needs a shared component to behave differently, who decides what changes? We can keep adding options to the component until it accommodates every team. That puts a decision about shared ownership and variation into the component's API. We could instead agree on where sharing ends and team-specific work begins. Either way, someone has to own releases, documentation, and exceptions. I've seen the shared system itself become a maintenance problem. A design system can improve consistency, but it is no silver bullet.
A project can look good without moving the bottleneck
I've migrated tests from Selenium to Playwright and introduced a new CI/CD step. These are concrete projects, easy to describe as engineering improvements. But the Selenium suite was slow, flaky, and unmaintained. Its condition should make us pause before assuming that a new test runner is the best use of our effort. Would the replacement be maintained? More fundamentally, are end-to-end tests where improvement would make the biggest difference to delivery right now?
The answer might still be to migrate. The point is to choose that work after looking at the whole path to production, rather than because test automation is our area and a migration makes a strong project. A new pipeline step deserves the same scrutiny: what delay or risk does it actually remove?
This is difficult for ambitious teams. Smart people want to build useful things. But their view and authority may stop at their own repository. They can create a library or replace a test suite; changing a service boundary or a handoff between teams requires agreement they may have no way to get. Their energy goes into the part of the problem they are allowed to solve, and the small triangle fills up with complexity from the larger one.
Engineering managers can reinforce this, even with good intentions. Someone wants a promotion and needs evidence of work “above and beyond”. A new library, framework migration, or pipeline step is easy to put in a promotion case. Removing a cross-team dependency may have more impact, but it is harder to scope, harder to attribute to one person, and harder to complete without support from other managers. So we can end up rewarding the most visible project instead of the most useful change.
Find the right scale for the work
I still care about code quality. A bad codebase can absolutely slow a team down. But before committing months to an improvement, I want to trace a real change from request to production. Where did it wait? Which boundary required negotiation? Are we solving that problem where it belongs, or encoding it in the code we happen to own? What would be faster or easier if the proposed project succeeded?
Sometimes the answer is a refactor, a better test suite, or a shared component. Sometimes it is a clearer contract, fewer handoffs, a simpler release path, or giving a team ownership of a whole slice of work. Engineers may not be able to change those boundaries alone. Managers can help them cross those boundaries and recognise that work as real engineering impact.
The triangle does not end at the edge of our repository. The same maintainability questions repeat as we follow a change through services, teams, and the organisation. Keep zooming out past the first wider view until you can see where the complexity belongs. Then remove it at that scale, instead of squeezing it into the part of the triangle you already control.