The Bug That Lived in the Walls: How AI Helped Me Finally Kill It
Legacy code doesn't break loudly. It quietly lies to you. Here's how I used AI to untangle a data-flow mess that had been rotting inside an iOS app for years, and why I couldn't have done it the same way without it.

Mobile engineer at BetaStudio. Mostly React Native, with enough Swift and Native iOS bruises to have a sense of humour about App Store rejections.

The Bug That Lived in the Walls
Legacy code doesn't break loudly. It quietly lies to you. Here's how I used AI to untangle a data-flow mess that had been rotting inside an iOS app for years, and why I couldn't have done it the same way without it.
The Bug That Lived in the Walls
Every iOS codebase has a room you don't open. A view controller doing seventeen things, a model that gets mutated in four places before the UI sees it, a screen that's "worked" for two years because nobody touches it. You ship around it and tell yourself you'll deal with it eventually.
We dealt with ours. It took an AI to make that doable.
What "data not flowing correctly" actually looks like
A big screen, central to the product, built early and patched ever since. Data moved through delegates, view models, and a couple of intermediate state objects. Somewhere along the way, values were being mutated. Not maliciously. Just because somebody on a deadline wrote a local reassignment that was the cleanest option at the time.
What that produced was a category of bugs that were almost impossible to reproduce. Users saw stale data. Edits sometimes saved, sometimes didn't. A save would succeed but the UI wouldn't catch up until the next app launch. Classic symptoms of data losing its shape somewhere in transit.
We fixed individual instances of these maybe a dozen times. We never fixed the root cause, because fixing it meant unpicking a screen tangled with business logic, network calls, local state, and rendering, all in the same place.
Why it stayed on the backlog
The honest answer is that the refactor was too vague to estimate. Not complex. Complex we can do. Vague. We didn't know what we'd find when we started pulling threads. We didn't know how many other parts of the app depended on the broken behavior. We didn't know whether the rewrite would actually be better than the original.
Anything you can't estimate stays on the backlog forever. The hard part isn't the refactor itself. The hard part is getting from "this is a mess" to a list of small, safe steps. That work never feels urgent when there are features waiting.
That's the part Claude did.
What I actually did with it
I walked through the screen with Claude. Pasted in the relevant files, described the data flow as I understood it, asked it to flag places where state could be getting mutated silently. Nothing it found was something I couldn't have eventually found on my own. But explaining the architecture out loud to something that asks follow-up questions forces you to be specific about the parts you'd been hand-waving over.
Out of that conversation: three places in the code where data was being modified mid-flow. Two I'd suspected. One I hadn't.
The next part was sequencing the refactor. A job like this has a few right orderings and several catastrophic wrong ones. Start with the wrong abstraction and you paint yourself into a corner. Claude helped me write out a step-by-step plan: what to extract first, what to leave alone, where to drop in a temporary adapter so the app stayed shippable while the work was happening. The plan wasn't perfect. I changed it as I went. But having a written sequence turned a vague month-long refactor into a list of small completable tasks.
Then it was the usual loop. Describe the change. Ask for a sanity check on the surrounding context. Make the change. Run the app. Read the diff. Not blindly, every line was looked at. But the feedback cycle was fast enough that I could spend the day in the codebase instead of in my own head.
What it looks like now
The screen has one source of truth for its data. State flows in one direction. The model that goes in is the model that comes out, unless something explicit and testable changes it. The previous setup, with half a dozen components having informal write access to the same shared state, is gone.
The obvious bugs are gone with it. More importantly, the whole category of bug is gone. We're not playing whack-a-mole with symptoms. The structural reason those bugs existed doesn't exist.
Four days of focused work. My pre-refactor estimate had been two to three weeks, assuming I could find the will to start.
What I'd say about this, working in iOS
Legacy code isn't hard because it's complex. It's hard because it's unfamiliar. The complexity is usually fine once you understand it. The problem is that understanding it takes time, and time is what you never have.
AI shortens the understanding phase. It's not smarter about your codebase than you are. But it can chew through unfamiliar code faster than you can alone, generate hypotheses about where the rot might be hiding, and turn a foggy "this is broken" feeling into a plan with discrete steps.
It doesn't replace engineering judgment. Every suggestion still needs a real eye on it. You still have to understand what you're merging. Teams that have stopped reading their own diffs eventually end up with codebases that look like it.
But for the specific problem of legacy code that's been too scary to touch, the math is different now. The work is still hard. It's just no longer undefined. And undefined is what keeps important refactors rotting on the backlog for years.
Ship the refactor.
More from the studio.
AI Won't Replace Developers — But Developers Who Use AI Will Replace Those Who Don't
The Subscription Studio Model: Why We Stopped Selling Projects and Started Selling Outcomes
Ship First, Architect Later: Why Premature Optimization Is Killing Your Startup
Want one of these built for you?
We're an eleven-person studio in Sarajevo, shipping product on a monthly subscription.