Almost every codebase we inherit suffers from the same disease. It is not bad architecture, though that is often present. It is not technical debt, though that is always present. The deeper disease is more cultural: years of small additions, each one individually reasonable, that compound into a product nobody can quite reason about anymore.

The mechanism is always the same. A customer asks for something small. The product manager agrees it is small. The engineer agrees it is small. The thing gets built. Multiply by a thousand instances over five years, and you have a product that is not actually a product but a museum of historical accommodations.

How the damage actually accumulates

Each individual feature does three kinds of damage, none of them visible in isolation. First, it adds complexity to the user interface — one more option, one more setting, one more menu item that some user somewhere relies on. Second, it adds complexity to the codebase — one more conditional, one more edge case, one more module that has to be kept in sync with everything else. Third, and most insidiously, it adds complexity to the organisation — one more thing the support team has to know, one more thing the documentation has to cover, one more thing a new engineer has to learn before they can be productive.

The first two kinds of damage are at least somewhat visible. A bloated UI looks bloated. A complex codebase has metrics. But the organisational drag is invisible until it is overwhelming. By the time the team notices that the product has become hard to explain to new hires, the explanation problem has been compounding for years.

The cost of a feature is not what it took to build. It is what it will take to live with, multiplied by every year it exists.

Why nobody pushes back

The mechanics of feature accumulation are not mysterious. They are deeply rational at every individual decision point. The product manager who pushed back on the last small request was the one who got the angry customer call. The engineer who said "this will be more complex than it looks" was the one who looked obstructionist. The CEO who asked "do we really need this?" was the one who watched a competitor ship it the next quarter.

The result is a kind of organisational gravity. Saying yes is locally easy. Saying no is locally expensive. The cumulative cost of all the yeses lands on someone, eventually, but rarely on the person who said them.

The practices that actually work against it

Make the cost visible at the moment of decision

The first practice that helps is making the long-term cost of a feature explicit when it is being decided. Not a vague "this will add complexity" — a specific accounting. What will this require in code? What will this add to the support burden? What will this take away from other things we could do? Most product reviews skip this step. The reviews that do not skip it produce different decisions.

Sunset features as deliberately as you ship them

The second practice is to take feature sunsets as seriously as feature launches. Almost every product accumulates features that nobody uses. Removing them is unglamorous, often technically tricky, and frequently resisted by the small minority who still rely on the feature. But removal is the only way to keep a product reasoning about itself over a long enough timeline.

We have done several engagements where the highest-leverage thing we did for a client was not to ship a new feature but to retire three old ones. The team's velocity on everything else recovered noticeably within a quarter.

Build a culture where "no" is safe

The third practice, the deepest one, is to build a team culture where saying "no, we should not build that" is not a career risk. This is easier said than done, because the people saying yes are visible and rewarded, while the people saying no often look like obstacles. The teams we have seen do this well have it modelled from the top — leadership that publicly walks away from features they could have shipped, and explains why.

The companies that have shipped iconically focused products are not the ones with better ideas. They are the ones with better discipline about which ideas they refuse.

What to do with a product that has already accumulated

For most teams reading this, the question is not "how do we avoid accumulating features?" — it is "we have already accumulated, what do we do now?" The honest answer is that this is one of the hardest problems in product engineering, and there is no clean playbook. But there are some moves that consistently help.

Inventory what you actually have

Most teams cannot list every feature in their own product. The first step is a literal inventory: every screen, every setting, every endpoint, every workflow. This sounds tedious. It always reveals more than the team expected.

Measure what gets used

For each feature in the inventory, measure honest usage. Not the marketing-friendly version that includes one-time exploration; the engaged-use version. Many products discover that twenty percent of their features account for ninety-five percent of their usage. The other eighty percent are a maintenance tax that is not paying for itself.

Sunset deliberately, with care

The features that are not earning their keep should be retired, but the retirement needs to be done with care. Notify users in advance. Provide migration paths where there is real usage. Be honest about the timeline. Done well, this can actually improve customer relationships — most users appreciate a team that is willing to take responsibility for the product's coherence.

Resist re-accumulating

The hardest part of cleaning up a feature-bloated product is not the cleanup itself. It is preventing the same accumulation from happening again over the next three years. This requires the practices above — explicit costing, deliberate sunsets, a culture where no is safe — and it requires the discipline to maintain them even when the next reasonable feature request lands on the desk.

The product that says no

The best products we have worked on, both inside our engagements and outside, share a quality: they have a clear sense of what they are and what they are not. Their teams can articulate the kind of customer the product is for, and — just as importantly — the kind of customer it is not for. This clarity is what makes saying no to feature requests possible without feeling arbitrary.

Most products that accumulate features have lost this clarity. The mission has fuzzed out into "be useful to whoever shows up." The roadmap has become a backlog of accommodations. Recovering from this state is possible, but it requires a willingness to draw lines that some users will resent.

The teams that draw those lines, in our experience, build better products and ship more confidently. The teams that do not draw them end up with what every aging product eventually becomes: a system that is hard to explain, hard to maintain, and hard to love.

Work with us

Have a project that needs senior engineering attention?

We work with founders and enterprise teams across Dubai, the US, and India. If something here resonates with what you're building, we'd be glad to talk.

Start a conversation →