DEV Community

Om Keswani
Om Keswani

Posted on

The "Safe" Deployment: How Feature Flags Became a Way to Avoid Writing Real Code

A few years ago, a team I worked with shipped a button. Just a button. "Download invoice." It sat behind a feature flag for eleven months. The flag had a flag. The flag had an owner who left the company. The flag's name was enable_download_invoice_v2_final_use_this_one.

That's not deployment safety. That's a storage unit for decisions nobody wants to make.

Feature flags are genuinely great. I've used them to ship risky changes to 1% of traffic, to kill a broken checkout flow in ten seconds, to run an A/B test without forking a branch. When used well, they're a seatbelt. But somewhere along the way, the seatbelt became a way to never actually drive.

Here's what I see now: a team afraid to write code, so they wrap everything in flags. New endpoint? Flag it. Changed the copy on a button? Flag it. Refactored a function that only one person calls? Believe it or not, flag. The flag becomes a substitute for confidence. Instead of asking "do we understand this change well enough to ship it?" the team asks "can we turn it off?" And those are not the same question.

The worst part is what flags do to the codebase. Every flag is a fork in the road. Two code paths, permanently. One of them is the code you're testing, the other is the code you're afraid to delete. Six months later nobody remembers which path is "real," so both get maintained, and the person who wrote the flag is long gone, and the flag is now load-bearing. You didn't reduce risk. You deferred it and added interest.

I once counted 340 active flags in a mid-sized service. Forty of them were enabled for 100% of users and had been for over a year. That's not a feature flag. That's a dead branch wearing a costume.

The uncomfortable truth: flags let you avoid the hard part of engineering, which is understanding your own system well enough to change it. If your test suite is solid, your observability is real, and your rollback story is honest, you don't need a flag for a copy change. You need a flag when the blast radius is genuinely unknown or irreversible—a schema migration, a pricing change, a new payment provider. Not for a button.

So here's a rule I try to follow: every flag gets an expiry date at creation. Every flag gets a cleanup ticket the same day it's created. If a flag lives longer than a quarter, it's not a flag anymore—it's a decision you're avoiding. Name it accordingly and put it on the roadmap.

Feature flags are a tool for managing uncertainty. They're not a substitute for removing it. If your deployment strategy is "we'll just turn it off," ask yourself why you're so sure the code is going to break. And then fix that thing instead.

Top comments (0)