You’ve been there. You’re staring at a massive, nested JSON configuration file, trying to figure out why a simple button isn’t showing up in production. You just want to ship a feature, but first, you have to navigate a labyrinth of environment variables, remote config servers, and legacy toggles that no one remembers setting. You think to yourself, “Why can’t I just hardcode this?”
And then the guilt sets in. The enterprise architecture gods have whispered in your ear that hardcoding is for amateurs. Real engineers use dynamic feature flag systems. But let’s be honest with ourselves: A feature flag isn’t a safety net; it’s a deferred decision masquerading as engineering.
We bought into the promise that feature flags reduce risk. Turn it off if it breaks, they said. But what happens when you have 500 of them? You don’t have a highly configurable system; you have a combinatorial explosion of untested states. You have a codebase where absolutely no one knows what is actually running in production.
I saw this firsthand when reverse-engineering a large enterprise app recently. The feature flag bloat was truly astounding. Dozens of flags toggling features that had been live for years, never cleaned up, silently adding technical debt. It wasn’t a technical strategy; it was a cry for help from an organization that couldn’t make up its mind.
Every flag in your codebase is a monument to a meeting where someone was too afraid to say “no”.
Excessive feature flag complexity isn’t a sign of advanced engineering. It’s an organizational symptom. It signals indecisive management and a fundamental lack of trust between the people writing the code and the people approving it. When you don’t trust your developers to ship safely, you give them a remote kill switch. When you don’t trust your product managers to commit to a release, you let them hide behind a toggle.
The cure has become worse than the disease. You deployed a complex remote config server to avoid a messy rollback, but now you have a messy runtime state that no one can debug. The “enterprise way” has you trading code simplicity for operational flexibility that you rarely, if ever, actually need.
Here’s the contrarian truth: for the vast majority of projects, hardcoding your feature flags isn’t just acceptable—it’s desirable. It’s simple, effective, cheap, and easy to understand. It doesn’t depend on an external service that can go down. It forces a different kind of discipline entirely.
When you hardcode a flag, you are making a commitment. You are saying, “This feature is done. It is part of the application.” If you aren’t sure about it, maybe it shouldn’t be in the codebase yet. Maybe it needs more testing, or maybe the business needs to make a decision before the engineering team writes a single line of code.
Simplicity isn’t the absence of power; it’s the presence of clarity.
If you’re a solo developer, or even a small team, the overhead of a dynamic feature flag system is almost never worth it. You don’t need A/B testing infrastructure for every minor UI tweak. You don’t need a kill switch for a feature that has been thoroughly tested in staging. You need clean, readable code that does exactly what it says.
So the next time you’re about to wire up a new toggle, ask yourself: are you managing risk, or are you just avoiding a difficult conversation? If it’s the latter, delete the flag. Hardcode the value. Ship the code. And force the decision.
FAQ
Q: But what if a feature breaks production? Don't I need a kill switch?
A: If your deployment pipeline is so slow that you need a runtime kill switch, fix the pipeline. Relying on flags to compensate for bad CI/CD is a band-aid on a broken leg.
Q: When is a dynamic feature flag actually justified?
A: Only when you genuinely need runtime control, like A/B testing user cohorts or rolling out a database migration to a subset of users without a redeploy. If it's just 'in case it breaks,' hardcode it.
Q: Are you saying all abstraction is bad?
A: No, I'm saying useless abstraction is bad. A feature flag that hasn't been touched in two years isn't an abstraction—it's a tombstone. Hardcoding forces you to bury the dead.