EST. 2003

The Skill of Removing Things

Every feature I've ever built started as a good idea. A real problem someone had, a reasonable solution, a clear path to implementation. And yet some of the best engineering work I've done has been deleting things that were, by every rational measure, useful.

Nobody celebrates that. We celebrate shipping. We celebrate the person who adds the feature, introduces the abstraction, wires up the new config option. But the person who removes something? They get a nervous code review and a "are we sure about this?"

Why We Keep Everything

Adding something to a system is easy to justify. More functionality, more flexibility, more things on the feature list. It feels like progress.

Removing something is the opposite. It feels like regression. Like admitting you were wrong. Like taking something away from people who might be using it. Even when the data says almost no one uses it, there's a gravitational pull toward keeping things. "What if someone needs it?" "It's already built, what's the harm?"

The harm is subtle and it compounds. Every feature you keep is a feature you maintain. Every option in a config is a code path that needs to keep working, needs to be tested, needs to be thought about when you build the next thing. Every toggle on a settings page is cognitive load for the person scanning it.

Software doesn't collapse under the weight of one unnecessary feature. It gets slow, confusing, and fragile under the weight of fifty of them.

Usage Is Not the Same as Value

One of the most dangerous assumptions in product engineering is: "it's being used, so it must be valuable."

Sometimes usage exists because something was available. Because it was the default. Because nobody questioned it.

I've seen internal dashboards with filters nobody understood but everyone dutifully clicked through because they were there. I've seen API parameters that existed because one customer asked for them two years ago and they've been carried forward through three rewrites.

The better question is never "do people use this?" It's "would anyone miss this if it were gone?" Most things won't be missed. Not because they were bad ideas, but because they've outlived their context.

The Real Cost Is Combinatorial

Here's something that's easy to underestimate: complexity isn't additive, it's multiplicative.

Consider an API like this:

createUser({
  role?: "admin" | "editor" | "viewer",
  sendWelcomeEmail?: boolean,
  inviteToken?: string,
  referralCode?: string,
  skipVerification?: boolean
})

Five optional parameters. Looks reasonable. But each one adds conditional logic, validation branches, test cases, documentation, and edge cases that interact with every other parameter. The surface area isn't five things - it's the combinatorial space of all their interactions.

Now imagine you check the data and find that no one uses referral codes, skipVerification only exists for internal testing, and sendWelcomeEmail is always true. You remove three parameters and the API becomes:

createUser({ role });

You didn't lose power. You collapsed an entire matrix of states that nobody needed. The validation got simpler. The tests got shorter. The next engineer who reads this function understands it in five seconds instead of thirty.

Feature flags work the same way. Each one feels safe in isolation. But two flags means four possible states. Three means eight. Five means thirty-two. Every flag you add doubles the space of behaviors your system can be in, and most of those combinations have never been tested together. Removing a flag isn't deleting code - it's collapsing possibility space.

Building for Futures That Never Arrive

We build abstractions "just in case" all the time. Generic repository layers. Plugin systems. Extensible configuration engines. Components parameterized for use cases we haven't encountered yet.

I've done this. I've written the clever generic version of something when the concrete version would've been half the code and twice as clear. And almost every time, the imagined future never arrived. Or it arrived differently enough that my abstraction was wrong anyway.

Premature flexibility doesn't save time - it spends time. It increases indirection. Indirection increases debugging time. And when you're three layers deep trying to figure out why a value is what it is, the abstraction that was supposed to help has become the thing slowing you down.

Most systems don't fail because they lacked early abstraction. They fail because they accumulated so much indirection that nobody could reason about them anymore.

Simplicity Is Earned

I don't think simplicity is a starting point. It's what you arrive at after you've built too much and then carefully, deliberately cut back.

The first version of anything is going to be too complex because you haven't yet learned what matters. You ship it, you watch how people use it, you notice which parts pull their weight and which don't. Then you start removing.

The best codebases I've worked in looked simple from the outside. But that simplicity wasn't the first thing the team did - it was the last. After building, observing, and then removing everything that didn't justify its existence.

How I Practice This

When something I've built feels cluttered or heavy, I ask myself one question: what would I remove if I had to remove one thing?

Not "what could I remove" - that framing invites you to say "nothing." But "what would I remove if I had to" forces a real evaluation. There's always an answer. A parameter pulling less weight than the others. A component adding friction without proportional value. A config option that exists because the team didn't want to commit to a decision.

Then I actually remove it. Not hypothetically. I delete it, look at the result, and sit with it for a day. More often than not, the remaining pieces breathe. The focus sharpens. The whole thing feels more intentional.

If no one notices, I just reduced entropy. Repeat.

The Decision Problem

A lot of toggles and options exist because a team avoided committing to a direction. "Should the sidebar be on the left or right?" Instead of picking one, you add a toggle. Now you have two layouts to maintain, two sets of edge cases, two paths that can break independently.

Removing options forces clarity. Clear UX direction. Clear API contracts. Clear product intent. Indecision scales complexity. Commitment reduces it.

This doesn't mean being reckless. Don't remove accessibility features. Don't remove security boundaries. Don't remove the things that make your product what it is. The point isn't minimalism for its own sake. The point is intentionality - knowing what deserves to exist and having the conviction to cut what doesn't.

The Hardest Part

The hardest thing about removing something isn't the technical work. It's the admission that comes with it: we were wrong. We overbuilt. We don't need this.

That's uncomfortable. But it's also how systems get better. The highest-leverage engineering skill isn't building more things. It's deciding what doesn't deserve to exist.

Saint-Exupery put it better than I can: "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."

That's the skill. Not adding. Removing. And having the taste to know the difference.