Blog
15

Your Developer Is Stuck in Technical Debt — and It's Probably Not Their Fault

Why New App Features Break Old Ones: Technical Debt

May 12, 2026

15 min read · The app launched on time. The first features shipped fast. Now every small change takes weeks, and the last release broke the login. Here's what's actually happening, and why it isn't really about the developer.

Something quiet has shifted in the way your project feels.

It didn't happen all at once. There isn't a moment you can point to. But where the first few months felt fast — features shipping every week, screens going live, momentum you could feel — the recent months have felt almost the opposite. Estimates that used to be days are now weeks.

A simple change to the checkout flow somehow takes down the profile screen. Your developer sends apologetic messages explaining what went wrong, and the explanations all sound reasonable in isolation, but the pattern is unmistakable. Things are getting harder, not easier. And you're paying more for less.

You haven't said anything yet, because you're not sure what to say. Your developer is clearly working — long hours, sincere messages, real effort. The app does still mostly work. Users haven't revolted. Whatever this is, it doesn't quite rise to a crisis. It's more like a low, persistent fog: every direction you look, the visibility is worse than it used to be, but you can't tell exactly when it got this way.

If this sounds familiar, what you're looking at has a name, and it's almost certainly not what you think it is. Your developer isn't slacking. They aren't getting worse at their job. The app isn't cursed. The thing that's happening is structural, it has happened to almost every successful app at some point in its life, and it has accelerated dramatically in the last two years because of how modern code gets written.

This article is about that thing. What it actually is. Why "new features keep breaking old ones" is the most reliable signal that you're dealing with it. And what your options are now — none of which involve firing your developer, and most of which don't involve throwing the app away.

The Velocity That Vanished

The first signal usually arrives as a feeling about pace, not about quality.

In the early months of a project, almost everything is new. Each feature is being added to a small, clean structure that hasn't had time to become tangled. A new screen is just a new screen. A new endpoint is just a new endpoint. The developer is fast because there's nothing in the way. They're not navigating around anything yet, because there's barely anything to navigate.

This is the period that makes both you and the developer optimistic. The velocity feels like a property of the team — of their skill, of the tools they're using, maybe of the methodology. It feels permanent.

You build budgets and timelines on the assumption that this pace will continue. You imagine the year-two roadmap because the year-one roadmap is clearly going to be done early.

Then, somewhere around month four or five — sometimes earlier, sometimes a little later — the curve starts to bend. Not catastrophically. Just enough to notice if you're paying attention. The estimate for a small feature comes back at a number that surprises you. You ask why; the answer involves "needs to be done carefully" or "we'll need to refactor part of X first." You shrug, you sign off, you move on.

By month seven or eight, the curve has bent unmistakably. Features that would have taken three days in month two take three weeks. The developer's apologies are sincere. They're not making more mistakes than before; they're just hitting more interactions every time they touch the code — places where one piece of the app talks to another, where what they're changing turns out to be connected to something they didn't expect.

By month nine or ten, every new feature ships with a small constellation of bugs in features that already existed. Sometimes the bugs are caught before release; sometimes they reach users. Either way, the cost of every change is now visibly higher than the feature itself would justify. You're not paying for the feature anymore. You're paying for the navigation around everything else.

This curve — fast, then slowing, then visibly straining — is one of the most honest signals available to a non-technical buyer. It's more reliable than any code review, any audit, any opinion. It's the shape of the structure underneath, showing through.

What Was Built Was Probably Built Correctly

It's tempting at this point to assume that the developer is bad, or the code is bad, or both. In our experience, neither is usually true.

If you look at the individual pieces of your app — a screen, a function, an API endpoint, a database query — most of them are probably fine on their own. They were written competently. They handle the cases they were asked to handle. If a developer reviewed each piece in isolation, they would mostly nod and approve.

This is especially true when an app has been built with the help of modern AI coding tools, which have become standard practice across the industry over the last couple of years. These tools are very good at producing locally correct code. Asked to write a login screen, they write a working login screen. Asked to write a checkout endpoint, they write a working checkout endpoint. Each generated piece is plausible. Each one runs.

The same is true, by the way, when an app is built by a rushed agency, or a small team under deadline pressure, or a junior developer left to do their best with no senior to lean on. The mechanism is different — humans rushing for time versus machines generating per-prompt — but the result is similar. You get an app made of competent local pieces, each of which solves a small problem on its own terms.

The question, then, isn't whether the pieces are good. The question is what holds them together.

It's Not the Parts. It's the Joints.

The thing that determines whether an app can keep growing is not the quality of its individual pieces. It's the quality of the joints between them.

Every app — every meaningful piece of software, really — is a collection of features that have to cooperate. The login flow has to hand the user off to the home screen, which has to load data that came from the backend, which had to authenticate the request using the token the login flow produced, which had to be stored somewhere the rest of the app can reach, which has to be refreshed before it expires, which means something somewhere has to know when "before it expires" is. None of these are particularly hard problems on their own. But the relationships between them — the assumptions each piece makes about every other piece — are the architecture of the app. They're the joints.

In a well-built app, those joints are deliberate. Someone designed the contract: this is how the login flow communicates with everything else; this is how the backend describes errors; this is what happens when the network goes down; this is what authentication looks like at every layer. The contracts are written down somewhere — or, more often, they're encoded into the structure of the code itself in a way that makes them hard to violate by accident.

In a hastily-built app — or an AI-assisted one with no human architect behind it — those joints are mostly improvised. Each feature was generated on its own. The login flow worked when the developer tested it. The home screen worked when the developer tested it.

The two of them appeared to work together when the developer ran them in sequence. But nobody designed how they were supposed to fit. The way they connect is whatever happened to work the first time it was tried, and that "happened to work" is the entire load-bearing structure.

This is invisible at first. Through the first few features, there are few enough joints that they don't matter much. By the fifth feature, the joints have multiplied — each new feature has to connect to several existing ones — and the implicit contracts between them have started to drift. By the tenth feature, the developer is spending more time trying to figure out why an unrelated screen broke than they spent writing the new feature in the first place. The features are still being added competently. The joints are quietly coming apart.

If you want one sentence to take from this article, it's this:

Pro Tip
the parts of your app are usually fine. The joints between the parts are where the cost lives.

Why "New Features Break Old Ones" Is the Diagnostic Signal

This is the symptom you're seeing, and it's the most reliable diagnostic you have.

In a well-jointed app, features are isolated from one another by design. Changing the checkout flow doesn't affect the profile screen because the profile screen doesn't depend on the checkout flow's internals — it only depends on a stable contract between them.

As long as the contract is honored, both can change independently. This is what "modular" actually means in practice. It isn't a buzzword; it's a property you can feel in your invoices.

In a poorly-jointed app, the boundaries between features are porous. The profile screen quietly depends on something the checkout flow happens to do in a particular order. Nobody planned that dependency; it just emerged when the two features were written without a clear contract between them.

As long as nothing changes, everything keeps working. The moment the checkout flow has to change — and it always has to change — the profile screen breaks, because the dependency the developer didn't know about has just been invalidated.

When you ask your developer how this happened, they often can't tell you. Not because they're being evasive — because they didn't write the dependency on purpose. They might not even know it existed until it broke. They're now reconstructing, by detective work, a connection that was never explicitly designed.

This is why "just be more careful next time" is not a fix. It isn't a discipline problem. It's a property of the structure. As the number of features grows, the number of possible undocumented connections grows much faster — roughly with the square of the number of features. A ten-feature app has, in the worst case, ten times as many possible feature-to-feature interactions as a five-feature app, not just twice as many. This is why the slowdown feels nonlinear. It is nonlinear.

A developer working inside this kind of structure can only do so much. They can be careful. They can write tests after the fact. They can manually trace through what might break. But they're swimming against the geometry of the problem. The structure was never designed to make changes safe, and no amount of individual carefulness can undo that.

This is also one reason why the obvious fix — "add tests now" — helps less than people expect. Tests written on top of an already-tangled structure mostly verify that the tangle still tangles the way it tangled yesterday. They lock in the current behavior, including the accidental dependencies. They make the next change safer in a small way, but they don't address the underlying geometry.

The Mobile/Backend Seam, Specifically

There's one joint that deserves its own section, because it's almost always the most fragile and the most expensive: the place where your mobile app talks to its backend.

A mobile app and a backend are two separate programs. They're typically written in different languages, by people (or AI sessions) working on them at different times, deployed independently, and updated on different schedules. The contract between them — what requests are valid, what responses look like, what errors mean, what fields are required, what happens when the user is offline, what auth tokens are expected, what gets retried, what gets dropped — is the most consequential interface in your entire system, and also the easiest one to leave unwritten.

In hastily-built apps, this contract is almost never designed. It's improvised on both sides. The backend developer (or generator) chose a response shape that made sense at the time. The mobile developer (or generator) wrote code that parsed exactly that shape. Both pieces were tested against each other and they worked. The contract exists, but it lives only in the heads of the people who wrote it — and increasingly, in nobody's head at all, if a generator wrote it.

Then someone needs to add a field. Or change an error code. Or support a new device that handles network drops differently. Or upgrade the backend framework. And the contract, which was never really agreed to, starts to fray.

This is where, in our experience, the worst class of "new feature breaks old feature" bugs lives. Not within the mobile app. Not within the backend. Between them. A new field added to the backend silently changes the shape of an existing response, and three mobile screens break in ways that are hard to reproduce. A new error code from the backend isn't handled on the mobile side, and the app shows a blank screen instead of a helpful message. An auth token format changes, and last week's iOS users can't log in anymore. The mobile developer says the backend changed. The backend developer says the mobile app never should have been parsing the response that way. Both are partly right, and neither of them wrote the contract down.

For a buyer, this is often the moment you realize you needed a team that understood both sides as one system. Building "a mobile app and a backend" is not the same as building "a mobile app that talks to a backend." The seam is the product. When the same team owns both sides, the contract between them gets designed with intent — even if it's never written out formally, it's at least coherent in someone's head, and someone is responsible for keeping it coherent. When the two sides are built by separate parties, or by AI sessions that never spoke to each other, the seam is the orphan.

The Three Conversations Worth Having

You don't need to make a decision today. What you need is more information, and that information comes from three conversations.

The first is with your current developer. Not adversarial — diagnostic. Ask, plainly: what does it cost us when we add a new feature to this app? Not in dollars, but in time and risk. A developer who has been honest with themselves about what they're working in will give you a straightforward answer. They'll mention specific areas that are fragile. They'll say which kinds of changes are safe and which ones make them nervous. They'll often acknowledge that the seam between the mobile app and the backend is where things tend to go sideways. If you get that kind of answer, you have a partner you can keep working with on a path forward. If you get vague reassurance or defensiveness, you've learned something else worth knowing.

The second is with someone independent. Not to replace your developer — to calibrate. A two- or three-day assessment from someone who didn't build the app can tell you, in plain language, how much of what you're feeling is technical debt, how much is the difficulty of the features themselves, and how much (if any) is correctable without major surgery. A competent reviewer can do this without disparaging the original work. The goal is information, not blame.

The third is with yourself, and it's the hardest one. What does your app need to do in the next eighteen months that it can't do well now? Two new integrations? A second user role? Offline support? A subscription model? Whatever it is, that list is what determines whether the current structure is good enough or whether something has to change. An app that has plateaued at exactly the shape it needs to be is fine living with some debt. An app that has to grow significantly is the one where the joints will give first.

These three conversations, taken together, give you the picture. You don't have to act on it yet. But you stop guessing.

What Your Options Actually Look Like

Most articles on technical debt jump straight to "rewrite it." That's a real option, but it's also the most dramatic one, and it's rarely the right first step. The honest range looks more like this.

Fix the worst joints in place. A skilled developer can usually identify the two or three points in an app where the cost of every future change concentrates, and improve those specific joints without touching anything else. This is often dramatic: a few weeks of careful work can take an app from "every change breaks something" to "most changes are safe." It's not a permanent fix, but it buys you a year of working comfortably while you decide what's next. Done well, it's also the cheapest intervention available.

Rebuild the seam between mobile and backend. If the problem really lives between your mobile app and your backend — and very often it does — you can leave both pieces largely intact and redesign just the contract between them. Define what every endpoint actually returns. Decide what every error means. Write down the auth flow. Add a small layer on each side that enforces the contract. This is much smaller than a full rewrite and much higher-leverage than feature-level patches. We've seen this single change transform an app's velocity more than any other intervention.

Rebuild one side. Sometimes the backend is solid and the mobile app is the problem, or vice versa. There's no rule that says they have to be rebuilt together. If you can identify which side is bearing the most weight, you can replace it while leaving the other side untouched. Our piece on whether your app needs a rewrite covers this in detail — particularly the "partial rebuild" option, which gets less attention than it deserves.

Full rewrite. This is the most dramatic option and sometimes the correct one. It's correct most often when the structure is so tangled that no incremental fix is durable, when the original developer (or the generator) is gone and nobody can decode the existing code, or when the business needs to go somewhere the current app structurally can't follow. The same article walks through this decision honestly, including the cases where rewriting is genuinely the wrong choice — and there are more of those than you might expect.

The right path depends on your situation, your budget, and what the app needs to do next. There is no universally correct answer. There is only the answer that fits your specific evidence — which is why the three conversations above come before the decision.

What a Sound Structure Actually Feels Like

It's worth knowing what you're aiming at, even at the level of feeling, because the difference between a sound structure and a struggling one is more felt than seen.

In an app whose joints were designed, not improvised, the cost of a new feature reflects the complexity of the feature itself — not the complexity of working around everything else. A new filter takes a week, not three. A new payment method becomes an addition, not a surgery. Updates to one part of the app don't cause inexplicable failures in another part. The developer can give you an estimate that they actually believe in, and the estimate usually turns out to be roughly right.

Underneath, what's happening is something a well-built app makes look effortless: the structure has a clear idea of what your business is. The code knows what a "customer" is and what an "order" is and what rules govern them, in a way that doesn't depend on which screen happens to be displayed. We've written more about this in our piece on clean architecture for business owners and in our overview of domain-driven design. Both articles describe the same idea from different angles: an app that knows what it's modeling can grow gracefully; an app that's just a stack of features can't.

You don't have to understand the engineering. You just have to know that the difference is real, that you can feel it in your invoices, and that the difference between the two kinds of apps is mostly decided very early — and recovered, when necessary, very deliberately.

A Note on the Person Who Built It

If your developer is reading this over your shoulder, this section is partly for them.

The shift in the last few years has been hard on developers. The tools have changed. The expectations have changed. Clients ask for things to be built faster than ever, and the tools that make "faster" possible — AI assistants, generators, accelerated frameworks — also make it dramatically easier to ship code whose joints aren't designed. A developer working with these tools can produce more code per day than they used to. Whether that code holds together over time depends on something else entirely: the developer's experience with the joints, with the contracts, with the structure underneath. That experience hasn't gotten easier to acquire. In some ways it's gotten harder, because there are fewer of the slow, hand-built projects that used to teach it.

If your developer is stuck right now, they are very likely not stuck because they're bad at their job. They're stuck because the structure they're working inside has reached the limit of what improvisation can carry, and recovering from that takes a particular kind of work they may or may not have done before. A short engagement with an outside structural eye is often enough to either give them what they need or confirm that you need a different partner for the next phase. Either outcome is fine. The point is to find out, not to keep guessing.

We've written elsewhere about what happens when a developer leaves entirely, and most of that advice applies in a softer form here. The questions are the same. The stakes are lower, because the relationship hasn't ended. But the underlying mechanics are nearly identical.

What Happens Next

The pattern you're seeing — new features breaking old ones, estimates creeping upward, your developer apologetic but unable to explain exactly what's going wrong — is one of the most common shapes a successful app takes when it's about a year old. It doesn't mean your app failed. It doesn't mean your developer failed. It means the structure underneath has reached a limit, and what got you here will not, on its own, get you to next year.

That limit is fixable. Sometimes it's fixable cheaply: a few weeks of careful joint-work and you're back to the velocity of month two. Sometimes it's more substantial: a partial rebuild, a seam redesign, occasionally a full rewrite. In every case, the right step is smaller and more diagnostic than the impulse suggests. Take the three conversations. Look at the evidence. Then decide.

The fact that you read this far is itself diagnostic. Most app owners who hit this wall react instead of investigate. The ones who investigate — who take the time to understand what they're actually looking at — almost always end up making a better decision and spending less money than the ones who don't.

Your developer is stuck. Your app isn't broken. The structure has reached its limit. Those three sentences look like bad news in isolation, but together they describe a normal, recoverable moment in the life of a business app. The good options are quieter than the dramatic ones, but there are more of them than the panic version of this situation would suggest.

That puts you in a better place than you think you're in.

Related Topics

technical debt mobile appAI rapid developmentnew feature breaks old featuresapp developer stuckmobile app technical debtbackend technical debtwhy is my app slow to updateapp maintenance cost compoundingmobile app architecture problemsAI-generated code technical debtwhy do app features break after updatemobile app and backend technical debt
Flutter & Node.js

Ready to build your app?

Flutter apps built on Clean Architecture — documented, tested, and yours to own. See which plan fits your project.

Clean Architecture on every tier
iOS + Android, source code included
From $4,900 — no monthly lock-in