DEV Community

Puneet Khandelwal
Puneet Khandelwal

Posted on

Feature Flags Aren't a Staging Environment

Feature flags sold us a lie. The pitch was simple: decouple deployment from release, wrap your experimental logic in an if/else block, and push to production with zero stress. If it blows up, flip the bit back. Simple.

Except reality caught up.

Engineering teams now treat production as their integration test bed. The logic goes that since the flag is set to false for ninety-nine percent of users, any underlying database migration errors, unhandled null pointers, or memory leaks are safely quarantined. That is not how distributed systems fail.

Let's look at what actually happens when you bypass staging because the code is behind a flag. You end up with state drift. Your staging database schema hasn't seen the new nullable foreign key constraint yet, because you didn't run the migration there. Your production environment has the schema, but the code path is dead until you flip the toggle. When you finally do flip it, the database engine throws a silent constraint violation that your unit tests missed because your mocks were too clean.

Here is a concrete example of the false security blanket we write every day:

async def get_user_dashboard(user_id: str):
 if await feature_flags.is_enabled("v2_dashboard", user_id):
 # This query hits a table that has zero indexes in production yet
 return await v2_heavy_aggregation(user_id)
 return await v1_fast_lookup(user_id)
Enter fullscreen mode Exit fullscreen mode

On paper, v2_heavy_aggregation is safe because the flag is off. In practice, the mere existence of that unindexed query path sitting in the deployed binary changes how the optimizer plans adjacent queries, or worse, the first user who gets the flag enabled triggers a connection pool exhaustion because the async task handling was never load-tested.

Feature flags are routing mechanisms. They are not architectural insulation. When you skip staging integration tests, you are betting that your dynamic configuration system is the only part of your infrastructure that will never have a race condition.

Here is the non-obvious implication of this habit. As AI-assisted coding tools pump out more boilerplate and feature flag wrappers at scale, the sheer volume of dead code paths in production binaries is skyrocketing. We are committing millions of lines of conditional logic that nobody has ever executed in a realistic, multi-service topology. The surface area for hidden bugs is expanding faster than our ability to write unit tests.

If your CI pipeline does not execute the code path with the flag turned on before it hits production, you aren't doing progressive delivery. You are playing Russian roulette with a remote control.

Fix your pipelines. Test the flag-on state in an environment that mirrors your production topology. Treat your toggles as what they are: temporary plumbing, not a replacement for engineering rigor.

Top comments (0)