I’ve spent years working with legacy mobile codebases. Some were stable, but most were "Jenga Towers" held together by hope and deprecated libraries.

Legacy code is a silent tax on your business and a headache for devs. Here are the top 6 horrors I’ve encountered and how I’d fix them in 2026:

A tall building with many balconies and balconies on the sides
Photo by Shai Lopez / Unsplash
  1. The Mother of all requests. It starts out with a home page that keeps growing and growing… While initially it might be a good idea to pre-load data - usually the bloat expands, as the complexity of the application grows. The user is left hanging while the app is loading ungodly amount information.

👉 Solution : Instead of having your good ol’ REST APIs you can go with GraphQL. In Kotlin Multiplatform this is easily done with Apollo Kotlin or a Ktor GraphQL client.

  1. The request stampede. Multiple REST requests at the same time, to the same server. There are specific cases where it makes sense to have multiple requests done at the same time, but very often it’s a result of poor planning.

👉 Solution : GraphQL can help here as well, by requesting only the needed data from the a single endpoint.

selective focus photo of brown and blue hourglass on stones
Photo by Aron Visuals / Unsplash
  1. Race conditions. By far the worst thing you can have in your app is a race condition. These things are incredibly hard to pin down. In one of the legacy projects I’ve worked on we once spent an entire week figuring out the root cause for a race condition. If your app starts having sporradic race conditions - this an equivalent of a “mayday” call on an aircraft.

👉 Solution : While there are no silver bullets for this one approach is to utilize UDF to hoist the state upwards to a single source of truth.

zebra standing on brown field during daytime
Photo by Neil and Zulma Scott / Unsplash
  1. Shadow Duplication. Sometimes apps get so big and the team turnover is so fast, that knowledge doesn’t get passed on. I once worked in a bug in a token refresh functionality for a legacy project, only to discover that another part of the app in the background was doing the same thing - but with a different framework.

👉 Solution : Modularization. Extract core business logic into a shared, platform-agnostic Kotlin module from the start. It makes duplication physically impossible.

red green and white candy cane
Photo by Girl with red hat / Unsplash
  1. Tight coupling (Founder’s debt). And by tight - I mean it was solid as a rock tight, and not in the good SOLID sense. Whenever you tried changing something - it felt like you’re playing Jenga. I remember working on transportation app that was initially written by the founder of the company - while he was still learning how to code. Yep, the core of the app was written by a junior developer, and left untouched for 10 years. You can imagine the delivery “speed”, to put it mildly.

👉 Solution : Thankfully, we have tools like DI tools like Koin and Hilt to fix these problems. It will take a great deal of untangling the code first, of course.

praying hand neon signage
Photo by Chris Liverani / Unsplash
  1. 500k+ LOC with no tests. Sadly, about 60-70% of legacy apps I’ve encountered didn’t have any tests. Such projects follow the “don’t touch it if it works” policy. But sooner or later you DO have to touch it if you want any features developed.

👉 Solution : No quick fix here, but you need to stop the bleeding. Enforce strict CI/CD gates on test coverage for all new code. The best time to plant a tree was 10 years ago - the second best time is today.

The key point: Every hour spent untangling code from a decade ago is an hour you aren't shipping features. My approach is to keep the code flexible with DI, modularize the core and establish clean architecture, share logic via KMP, and turn that Jenga tower into a foundation.

Which of these "Horrors" is currently lurking in your production app? Let's discuss in the comments.