Let’s start with a universal truth: every developer, when encountering a piece of existing code, immediately thinks, “Who wrote this trash?” And if the answer is “me,” the follow-up is “Well, golly-damn, how I’ve grown since then.” It’s instinct. Like salmon swimming upstream or clients adding scope.
But here’s the thing: if the software is running today, in production, doing an actual job… it’s legacy. It might not feel like it. It might still be using a trendy JavaScript framework with a name like a craft beer. But if it exists, it’s already aging.
Legacy Means It Works
“Legacy software” has become shorthand for “bad” or “outdated,” but that’s just often dev-speak for “I didn’t write this, and I don’t want to understand it.” In reality, legacy means functioning. It’s the survivor of a thousand sprints and stakeholder whims. If it’s still running, it beat the odds.
Now, don’t get me wrong. Most systems that are dragged to MY door to look at are not actually functioning. Someone calls my company because their software was drooling on the floor and causing embarrassment. The shit that I’ve seen would blow, your, mind. But that’s not what this blog is about.
We’re talking about software out there, living in the wild! It takes time to build something that doesn’t fall over every time a user sneezes. It takes even longer to secure it, document it, get it to interoperate with ten other weird systems, and stop everyone from rage-deleting it. So by the time any piece of software is stable, it’s already old. It’s already legacy.
Not Built, But Grown
Most production code didn’t emerge from the forehead of Zeus. It was assembled by overcaffeinated teams juggling client demands, budget changes, and the immortal words: “We just need this to go live next week.” Some team (who all argue about A24 movies) wrote it to work, not to impress some future prospective employer reading the commits on GitHub.
And even the “perfect” systems? The ones designed by IBM consultants with flowcharts written in blood? Those are more legacy. They’ve been running longer than some of your engineers have been alive. The problem isn’t that they’re bad. The problem is that they’re good enough to keep running, and nobody alive today knows how to update them without summoning Cthulhu via an old terminal. One of the great pleasures of my career was working with an 82-year-old Senior Software Engineer to refactor a giant risk management system running on IBM AS400. That thing was unhackable, had ran for 50 years straight, and when it crashed no one remembered how it worked.
Rewrite and Regret
Every developer has had the impulse to scrap an existing codebase and rewrite it from scratch. That’s the dream: clean slates, elegant abstractions, no weird edge cases left behind by Steve in Accounting who once “fixed” a bug with a semicolon and a prayer.
But rewriting software just means writing new legacy. You’re trading one set of sins for another, and now you own the sin. Worse, it probably won’t even ship with all the features the old version had, because, well… you forgot how much of that was important.
There Is A Right Way, Of Course…
That’s to slowly but surely rewrite independent modules, send out press releases about your new fancy features, ride the wave of glorious press coverage to your next PE or VC round, and replace the dinosaur bit by bit. But that’s not what this blog is about either.
AI: Now With More Legacy
And now there’s AI-generated code. These systems can crank out tens of thousands of lines in a week, and some of it looks legit… at first glance. But under the hood, AT LEAST FOR NOW, it’s often not great.
The speed of generation doesn’t mean quality, it just means you’ll reach the legacy stage faster. “Move fast and break things” is now “Move fast and drown in your own tech debt.”
Embrace the Patchwork
Software is patchwork. It has scars. It evolves, like a Pokémon that starts off as a fish and eventually becomes a bear or something. It carries around evolutionary leftovers that don’t make sense anymore but can’t be removed because five other systems depend on them. And that’s okay.
The job isn’t to hate it. The job is to shepherd it. To improve it where you can, reinforce the parts that work, and refactor the parts that need a little love.
Set Expectations, Stop Whining
So let’s stop acting like “legacy” is a four-letter word. Let’s teach our stakeholders, our clients, and ourselves that software is a living, breathing mess, and that’s fine. I always use the metaphor that software is like a garden. It requires constant care, weeding, and occasionally cutting bits out and putting in new soil. Feel free to steal that.
Every codebase is a conversation across time. Every app is a reflection of the best we could do back then. The question isn’t “Is this perfect?” It’s “Does this work well enough to keep going, and how can we make it better without starting over?”
Unless it was built by Sourcetoad. Then, of course, it’s perfect.
(Except when it’s not. We refactor it too.)



