7 Common Mistakes When Adopting Feature Toggles That Developers Make

Last updated Mar 1, 2026 Published Mar 1, 2026

The content here is under the Attribution 4.0 International (CC BY 4.0) license

Join Our Community

Connect with developers, architects, and tech leads who share your passion for quality software development. Discuss TDD, architecture, software engineering, and more.

→ Join Slack

Feature toggles, also called feature flags, are a technique that allows teams to modify system behavior at runtime without deploying new code (Hodgson, 2017). When used well, they enable trunk-based development, progressive delivery, and safe rollouts to production. When not managed properly, they become a source of defects, operational confusion, and accumulated technical debt that is hard to undo.

Feature toggles in detail

I go deeper on the subject about feature toggles in a dedicated section on this blog. You can find it here: Feature Toggles.

Pete Hodgson’s seminal article on Martin Fowler’s blog (Hodgson, 2017) and the foundational guidance in Continuous Delivery (Humble & Farley, 2010) both warn that the benefits of feature toggles come with real costs if teams do not manage them with discipline. Empirical studies confirm this: unresolved feature flags in active codebases correlate with increased defect density and elevated maintenance burden (Mahdavi-Hezaveh et al., 2021).

The seven mistakes described below are grounded in the experience and evidence observed across teams that have adopted feature toggles in their software development practices.

Treating toggles as permanent configuration switches

The most common mistake is allowing a temporary release toggle to drift into a permanent configuration parameter. A release toggle is meant to be short-lived, used to decouple deployment from release and then removed once the feature is fully rolled out (Hodgson, 2017). When teams leave those toggles in the codebase indefinitely, the code must continue to support two paths for every toggle that was never cleaned up.

Hodgson distinguishes between release toggles, experiment toggles, ops toggles, and permission toggles, each with a different expected lifespan (Hodgson, 2017). Conflating these categories leads teams to apply the same lifecycle assumptions to all of them, which usually means nothing gets removed. A release toggle that graduates into a permanent config value without intentional reclassification is a design decision that might have unintended consequences and lead to maintenance challenges.

The downside is that the codebase becomes littered with toggles that no longer serve their original purpose, creating dead code paths and increasing cognitive load for developers. It also makes it harder to reason about the system’s behavior, since the presence of a toggle implies that both paths are valid and may be exercised in production.

The alternative is to treat release toggles as strictly temporary and enforce a disciplined removal process. Teams should:

  • Track each release toggle with an explicit owner and planned removal date
  • Add automated reminders or issue tracker tickets to revisit and remove toggles after rollout
  • Regularly audit the codebase for toggles that have outlived their purpose
  • Reclassify toggles that must remain (e.g., as ops or permission toggles) with clear documentation and intent

Empirical studies and industry guidance recommend integrating toggle cleanup into the deployment workflow, ensuring that toggles do not become a source of technical debt (Hodgson, 2017) (Humble & Farley, 2010) (Mahdavi-Hezaveh et al., 2021). This approach preserves the benefits of feature toggles while minimizing their long-term risks.

Proper toggle classification is also essential. As described in Martin Fowler’s blog post by Pete Hodgson, toggles should be categorized according to their intended use:

  • Release toggles: Short-lived, used to decouple deployment from release
  • Experiment toggles: Used for A/B testing or canary releases, typically temporary
  • Ops toggles: Operational controls, may be long-lived
  • Permission toggles: Used to enable features for specific users or groups, often persistent

Categorizing each toggle at creation time clarifies its expected lifespan and management strategy. Teams should document the toggle type in code and in tracking systems, and periodically review toggles to ensure their classification remains accurate. This practice reduces ambiguity, supports disciplined removal, and aligns with industry recommendations (Hodgson, 2017).

Failing to test both toggle states

A feature toggle introduces a conditional branch in the code path. Both the enabled and disabled states represent valid system configurations that users (or operators) may encounter. Testing only the enabled state, or only the disabled state, means the untested path can silently regress.

Humble and Farley (Humble & Farley, 2010) emphasize that the deployment pipeline must validate every configuration the system can assume in production. For feature toggles, this means running the automated test suite against both states as part of the pipeline. (Mahdavi-Hezaveh et al., 2021) found that unresolved feature flags in open source systems were frequently associated with increased defect density, a pattern that traces partly to the untested alternate state being left exposed in production.

What does "testing both states" require in practice?

For each toggle, the test suite should contain at least one test scenario where the toggle is enabled and one where it is disabled, covering the behavioral contract of the feature boundary. Integration tests and end-to-end tests should parameterize toggle state so that the pipeline can exercise each combination. When the number of toggles grows, combinatorial explosion becomes a risk, which is one more reason to keep the number of active toggles small.

Scattering toggle checks throughout the codebase

When teams check the toggle state at every point where behavior diverges, the toggle logic permeates the entire codebase. Removing the toggle later requires locating and deleting dozens of scattered conditional checks, many of which may be several levels deep in the call stack.

Hodgson recommends concentrating toggle checks at a single decision point and using that decision to select among strategies or collaborators, rather than threading an if (featureEnabled) check through every affected layer (Hodgson, 2017). This encapsulation reduces the surface area of the toggle, makes it easier to reason about its effect, and limits the scope of changes needed when the toggle is cleaned up.

The cost of this mistake is not immediately visible. It becomes apparent when the feature is ready to go fully live and the team realizes that removing the toggle requires touching dozens of files and testing all of them.

No lifecycle management for toggles

A toggle without an explicit owner, a stated purpose, and an expected expiry date will not be cleaned up. (Mahdavi-Hezaveh et al., 2021) studied open source systems and found that a large portion of feature flags were never resolved, remaining in the codebase well beyond any plausible release window. These zombie flags add dead code paths, increase cognitive load during code reading, and create false complexity for developers joining the team.

Treating toggle lifecycle as a first-class concern means recording, for every toggle created, the name, purpose, category (release, experiment, ops, permission), the owner, and the date by which it should be removed or explicitly promoted. This record can live in the toggle management system or in a team backlog. What matters is that it exists and that the team has a process for acting on it.

Using feature toggles as a substitute for trunk-based development discipline

Feature toggles enable trunk-based development by allowing incomplete work to exist in the main branch without affecting users (Humble & Farley, 2010). However, some teams adopt toggles as an excuse to avoid small, incremental commits. Instead of decomposing work into thin vertical slices, they wrap large blocks of in-progress code behind a toggle and commit it all at once.

This defeats the purpose. Continuous integration requires frequent integration of small changes, not infrequent integration of large batches hidden behind a flag (Forsgren et al., 2018). When a large batch finally lands on the main branch, the integration risk is the same as with long-lived branches, the toggle simply defers it instead of eliminating it. Teams that practice true trunk-based development use toggles to hide incomplete but working increments, not to avoid the discipline of incremental delivery.

The relationship between feature toggles and branch strategy

Feature toggles and branching strategy are complementary, not interchangeable. A team on trunk-based development uses short-lived branches (ideally less than one day) merged to trunk frequently. Feature toggles allow features that span multiple commits to remain invisible to users while the code is fully integrated and tested. The discipline of small commits still applies to the code behind the toggle. Skipping that discipline in favor of a single large commit behind a flag simply moves the integration problem to a later date.

This approach in teams adopting pair programming are more likely to reduce the bach and integration risk. Take an example of a feature that is being worked on by two developers. They might use short-lived branches and switch the driver role every pomodoro cycle. The feature toggle allows them to integrate their work any time.

Embedding toggle logic deep inside domain or business rules

The location of a toggle check determines how much of the system it couples together. Placing a toggle check inside a domain object or a business rule means that the domain logic is now aware of infrastructure concerns (configuration, experiments, permissions). This violates the separation between what the system does and how it is deployed (Dittrich & Brucker, 2016).

A toggle check belongs at the boundary of a feature, not inside its internals. The entry point to the feature path (a controller, a use case boundary, a factory) is the place to read the toggle state and delegate to the appropriate implementation. The domain objects behind that boundary should not need to know that a toggle exists. This design makes the toggle easier to remove, because the conditional is in one place, and it keeps the domain model free of deployment concerns.

Neglecting toggle consistency in distributed systems

In a system composed of multiple services, a single logical feature may span several services. When each service reads the toggle state independently, there is no guarantee that a request flowing through multiple services will see a consistent toggle state. Service A may evaluate the toggle as enabled while Service B evaluates it as disabled, producing a request that partially executes the new code path and partially executes the old one.

Humble and Farley (Humble & Farley, 2010) note that configuration consistency across services is a foundational requirement for reliable continuous delivery. For feature toggles specifically, this means using a centralized toggle service that all services query, and designing the toggle evaluation so that the decision made at the entry point of a request propagates through to all downstream services involved in that request. Without this, the system can exhibit behaviors that are impossible to reproduce in test environments, where services are typically tested in isolation.

Using dependent toggles without considering the combinatorial explosion

When multiple toggles interact, the number of possible configurations grows exponentially. If a team has 5 toggles, there are 32 possible combinations of enabled/disabled states. If those toggles are not independent (e.g., Toggle A only makes sense if Toggle B is enabled), then the team must also consider the dependencies and constraints among them. If the team does not have a strategy for managing this combinatorial explosion, it can lead to untested configurations, unexpected interactions, and increased complexity in both the code and the testing strategy.

Teams should limit the number of active toggles at any given time, and when multiple toggles are necessary, they should design them to be as independent as possible or explicitly document their interactions and dependencies.

Conclusion

Feature toggles are a mechanism used for decoupling deployment from release and for enabling progressive delivery. The mistakes described above are not hypothetical. They are documented patterns observed in production systems and analyzed in empirical research (Mahdavi-Hezaveh et al., 2021).

The underlying discipline required to use feature toggles well is the same discipline required to practice continuous delivery: small changes, tested thoroughly, with clear ownership and short feedback loops (Humble & Farley, 2010; Forsgren et al., 2018). Toggles amplify whatever habits a team already has. Teams with delivery discipline will benefit from them. Teams without that discipline will accumulate the debt they create.

References

  1. Hodgson, P. (2017). Feature Toggles (aka Feature Flags). https://martinfowler.com/articles/feature-toggles.html
  2. Humble, J., & Farley, D. (2010). Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley Professional.
  3. Mahdavi-Hezaveh, R., Ajmeri, N., & Williams, L. (2021). An Empirical Study of Unresolved Issues in Software Systems with Feature Flags. Proceedings of the 43rd International Conference on Software Engineering, 312–324. https://dl.acm.org/doi/10.1109/ICSE43902.2021.00038
  4. Forsgren, N., Humble, J., & Kim, G. (2018). Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations. IT Revolution. https://itrevolution.com/product/accelerate/
  5. Dittrich, A., & Brucker, A. D. (2016). Feature Flags: Towards Principled Decompositions of Feature Toggles. Proceedings of the International Conference on Software Architecture Workshops, 82–89. https://doi.org/10.1109/ICSAW.2016.31

About this post

This post content s was assisted by AI, which helped with research, curate content and code suggestions.

You also might like