Developer Productivity

What a Feature Really Costs After You Ship It

Someone asks how long a feature will take. You say three weeks, and three weeks is roughly right. Nobody asks the other question, and the other question is the expensive one: what does this cost every month for as long as it exists?

The build is a one-off you can see and defend in a planning meeting. The carrying cost is a recurring bill nobody invoices you for, spread thinly across dependency upgrades, incidents, onboarding, CI minutes and the small hesitation every future change acquires. It does not show up as a line item, so it gets ignored — right up until a team of six can't ship anything in under a month and nobody can point at what changed.

The takeaway up front: the cost of a feature is dominated by how it interacts with everything else, not by how hard it was to write. That is why two features with identical estimates can differ tenfold in what they cost over their lifetime, and why the cheapest thing you do all quarter may be deleting something.

The bill nobody invoices

Once a feature is live, it starts taking a small cut of everything the team does. None of these is dramatic on its own:

  • Every dependency upgrade now has one more thing to check.
  • Every schema change now has one more consumer to migrate.
  • Every incident now has one more suspect.
  • Every new hire now has one more concept to learn before they can be trusted with the codebase.
  • Every test run pays for its tests, on every branch, forever.
  • Every support conversation may now route to it.
  • Every refactor that touches its area now costs more, or gets abandoned.

Multiply small taxes by the number of features and the number of changes, and you get the thing teams describe as "the codebase got slow." Codebases don't get slow. Change amplification does.

Where the money actually goes

Change amplification

This is the largest cost and the least visible. The relevant question about a feature is not how much code it is, but how many other places have to be touched when something moves. A feature with one entry point, its own module and a clear boundary is cheap to carry. The same behaviour threaded through five call sites, two background jobs and a special case in the auth middleware is expensive forever, even though both took three weeks to write.

Coupling, not size, is what you pay for monthly.

Data outlives code

Code is cheap to delete. The table it wrote to is not. Schemas acquire consumers you didn't plan for — a report, an export, a downstream job someone wrote without telling you — and every one of those makes the next schema change harder and slower. If a feature creates a new table, a new column with meaning, or a new event stream, you have committed to migrating that shape for as long as the product exists. Zero-downtime database migrations is the mechanics of paying that bill; the point here is that the bill exists and arrives repeatedly.

Data you keep is also data you have to secure, back up, restore, export on request, and delete when someone asks.

Operational surface

A feature that can fail in production needs a way to know it failed. That means logs somebody will actually read, at least one signal that reflects the user-visible outcome, and — if it can wake someone up — an alert with a documented response. Each of those is cheap to add and permanent to maintain. Alerts nobody tunes become alerts nobody trusts, and an ignored alert is worse than no alert, because it costs attention and buys nothing.

The honest accounting: an alert is a subscription, not a purchase.

Test-suite time

Tests are worth having, and they are not free. Each one adds to the wall-clock time between pushing a branch and knowing whether it worked, and that number sets the tempo of everything the team does. Slow suites push people to batch changes, and batched changes are riskier to review and harder to debug when they break.

Flaky tests are worse than slow ones. A test that fails occasionally for no reason trains the team to re-run rather than read, and that habit is what lets a real failure through.

Cognitive load and onboarding

Every feature is something a new engineer eventually has to hold in their head, or at least know exists so they don't reimplement it. Surprising behaviour costs the most: the special case, the config flag whose two values do genuinely different things, the endpoint that behaves differently for one customer. Those get explained in person, repeatedly, because they were never written down.

The leftovers

Feature flags that outlived their rollout. Dead config. A migration path kept "just in case." Two ways of doing the same thing, because the second was added but the first was never removed. Leftovers are pure cost — they carry all the maintenance burden of a feature and deliver none of its value. Tooling accumulates the same way, which is what auditing your team's tool sprawl is really about.

What looks expensive but is not

Deleting things. A week spent removing a feature nobody uses looks like a week with no output. It buys back a permanent share of every future change. Deletion is the highest-return work most teams never schedule, and it is the only work that makes the codebase strictly smaller.

A boring, well-maintained dependency. Paying for something dull — a managed database, a queue, an auth provider — often reads as an avoidable expense next to a weekend's work. The weekend is not the cost. The cost is the years of patching, edge cases and pager rotation that follow, and those are the parts nobody estimates.

Writing the removal path before you ship. Twenty minutes to decide how a flag gets retired, how the data gets cleaned up, and what "off" means. It looks like ceremony. It is the difference between a feature you can reverse and a feature you are stuck with.

Saying no. The cheapest feature is the one that was never built, and it is the only one with zero carrying cost.

What is worth paying for

  • A boundary. One module, one entry point, one place to look. Boundaries are what let you delete something later without an archaeology project.
  • A named owner. Not for blame — for the upgrade nobody wants to be responsible for. Unowned code is where carrying cost accumulates fastest.
  • Tests at the layer that survives refactors. Tests written against behaviour keep paying. Tests written against internal structure have to be rewritten every time you tidy up, so they charge you twice.
  • One signal that reflects the user's experience. If you keep one piece of observability per feature, keep the one that tells you whether the thing works for a real person.
  • An expiry date on every flag. A flag with a removal date is a rollout tool. A flag without one is a permanent branch in your logic.

What is not worth paying for

Generality you cannot name a second use for. An abstraction layer over a dependency you will never swap. A configuration option added because someone might want it, which now has to be tested in both states forever. Each of these is paid for immediately and repeatedly, in exchange for a benefit that usually never arrives.

The same caution applies to code that arrives faster than it can be understood. Generated code raises write speed but not carrying cost — the review still has to be real, which is the point of reviewing AI-generated code properly.

Estimating carrying cost before you commit

Add five questions to the estimate. They take ten minutes and they change decisions:

  1. Who owns this in a year? If the answer is "the team," it's nobody.
  2. What new data does it create, and who will depend on that shape?
  3. What happens when its main dependency releases a breaking version?
  4. How does it fail, and how would we know?
  5. How do we turn it off? If there is no answer, you are not shipping a feature, you are adopting one.

A rule of thumb, offered as a rule of thumb and not a measurement: assume a feature you keep will cost more over its life than it cost to build. You do not need a precise multiplier — you need the habit of putting a non-zero number next to "and then we maintain it," because the number teams currently use is zero.

This is also the honest frame for deciding what to build in-house versus what to buy or hand to someone else. Carrying cost is the part that stays with you either way, and it should be explicit in the comparison — see when to outsource software development.

FAQ

Is this just an argument against building features? No. It is an argument for pricing them correctly. Plenty of features are worth several times their carrying cost. You just can't tell which ones while you pretend the cost is zero.

How do I justify deletion work to a manager? Frame it in throughput, not tidiness: this removal takes one week and makes every future change in this area cheaper. Name the specific changes it unblocks.

Is technical debt the same thing as carrying cost? Related but not identical. Technical debt is usually a shortcut you chose. Carrying cost applies to well-built features too — clean code still has to be upgraded, tested, learned and operated.

What is the single highest-leverage habit here? Deciding how a feature gets removed before it ships. It costs almost nothing at the start and is nearly impossible to retrofit.


Estimate the build, then estimate the years after it. Write down who owns the feature, what data it creates, how you would know it broke, and how it gets turned off — and let those answers carry as much weight as the three-week number. For more engineering guides that take the maintenance side seriously, read TheAppCode.

Comments are disabled for this article.