Retiring a Feature Is Three Decisions, Not One
Deleting features that worked taught me three things: judge code and data apart, try a feature by principle rather than quality, and keep nothing half-alive.
Building a feature has a procedure. You define the problem, cut the scope, build it, ship it. Retiring one usually has none. When nobody seems to use it, it comes out of the menu, and the code stays behind for some later cleanup that rarely arrives. That is how I had been doing it too.
Recently I deleted things that worked in two products: three features in somesignal, a service that reads a relationship between two people, and a whole deployment target in DUGOUT 9, a fan project for Korean professional baseball. What I learned along the way is that "retire" is one word covering three different decisions: what to keep, what to judge by, and how far to cut.
Bold with code, conservative with data
somesignal had a feature that recommended a place for two people to meet. It carried 2,780 lines of backend code, 126 hand-picked places, a 342-line document describing how places were collected, six operations scripts, and ten dedicated tests. None of it was poor work. But almost nobody was on the receiving end, and not a single piece of feedback had come in. I had already frozen collection. Freezing stopped the debt from growing; it did not pay it back. The place data kept ageing, and the code was still being read and tested.
So I deleted it: code, seed data, scripts and tests, around 12,000 lines. What I did not drop were the database tables and columns. The code was ours to stop using. The records already stored were other people's, and that is a different question. Past plans that referenced a place are still read, so nothing on screen breaks.
Treating the two as one block gets one of them wrong. Protect code the way you protect data and you keep paying for it; delete data the way you delete code and there is no way back. Deleted code returns from version history whenever you want it. Deleted records do not. The costs run in opposite directions, so the decisions have to be made separately.
Try good features against the principle
Around the same time, somesignal adopted a new principle: you see your result first, without signing up. Inside the relationship map, though, were two features — tapping the other person's star, and question cards. Both only work if the other person is a member and opens the app. "You don't need to sign up" and "go and summon the other person" could not both be true on the same screen.
Neither feature was broken. Each simply contradicted the principle we had just chosen. Reviewed one at a time, features like these usually survive, because "is this well built?" is easy to answer yes to, and easier the better it is built. The question that mattered was "does this hold under our principle?", and these two did not. Keeping the product from running on two principles at once mattered more than keeping any one feature.
The principle also decided how far the cut went. The way to create new taps closed, but what was already there stays visible in the timeline. Plans to meet in person stayed, because they hold under the principle and produce something real. I also fixed what had leaned on the removed features: a notification switch for something that could no longer arrive would have been a control that controls nothing, and a screen that quietly misleads.
Keep nothing half-alive
DUGOUT 9 once had a second deployment target. It had no database or scheduler attached, so anyone who arrived at that address left no record behind. Its links and build configuration, however, were still live.
There were two options: keep it as "the version without records", or delete the build target outright. Keeping it looked like the cautious choice. But a second address that half works means share links, search results and the app shell keep pointing at one or the other, and that confusion never shows up as an error message. It shows up as a person's history quietly not being there. So I deleted it and pinned the product to a single home.
It is tempting to think the cost of "leave it running for now" is upkeep. The real cost was ambiguity. A half-working second path is not an option kept open; it is a surface for mistakes.
Taking things out has a design too
Set side by side, the three decisions have one thing in common. None of them was "delete or not". Each was "by what rule, and how far": the line between code and data, the principle a feature is measured against, the boundary that allows no half-measures. Delete without drawing that line and you take too much or too little.
I want to spend some of the attention I give to building a feature on retiring one. Taking something out is not cleanup after a failure. It is one more way of deciding what the product is.