Move Fast, Break Nothing*: Feature Flags as Enabler of Continuous Deployment
(*Most of the time.) The Why and the How of making Continuous Deployment work for you by using Feature Flags to manage releases.

The Conflict of Speed vs. Stability
If there are axioms in software development, then the duality of users wanting more features and a bug-free, always-available system must be one of them. An unstable system is the quickest way to churn users, but without features, you are bound to get disrupted by innovative competitors. So, there is no way around it: you have to release changes. The trick is ensuring these changes have the desired effect and are well-accepted by users. And challenging things? Those require a strategy.
In our case, we fight for market share with companies sometimes 10x bigger than us. Instead of shying away, we turn this into a competitive advantage because we can do things differently. We can adapt and align the whole organization on a new vision much more quickly. To strengthen this edge, we decided to use Continuous Deployment (CD) for all changes to our product, a B2B SaaS platform for process digitalization.
The premise of CD, as you might read on social media or in explanatory videos, is usually this:
Code a new change.
Run automated checks.
Once passing, deploy the new version to production.
This sounds like the antithesis of stability. As with any design, product, or even art, the first version of anything new requires feedback and rework to stand the test of its beholders.
So you say: “We should deploy the first version directly to all users?!”
The answer is no, you should not. You should have a strong release process leveraging feature flags to decouple a release from deployment. And just as importantly, you should align this process with your documentation, marketing, and customer communication. You will understand how to adopt the 5 segment release process after reading this article.
Why We Use Feature Flags
Feature flags allow us to get features into production and into the hands of Product Owners, Presales, and early adopters without disrupting other users. We can iterate rapidly, testing and gathering feedback in the same production environment our users live in every day. We do this without a new version, everything is on main. This offers three major advantages:
Feedback without High Complexity: Users can test features early, yet we have no maintenance of extra environments since we always keeping only a single version up and running.
Minimized Risk: We can deploy multiple small changes instead of big bang deployments, which reduces risk exposure and emphasizes steady flow of work within the organization.
Release Control: The teams decide on their own when a feature is ready. They own their flags and the entire release process. They update documentation and set release dates without the pressure of a deadline because releasing a feature is just a single boolean configuration.
Both our frontend and backend components are integrated with Flipt, a flag management tool. With a single boolean configuration, we manage the narrative and control exactly when a feature appears to users with a single boolean configuration. It also puts an end to the "Has this been enabled in environment X?" questions; the flag state tells the story.
Feature flags vs. Tiers - it is important to distinguish flags from product tiers. Flags play their role strictly in release control, not for long-term configuration of customer licensing, contractual agreements or usage based pricing. Flags have to be removed not long after a feature has been released as the codebase would otherwise get littered with if-else statements.
Feature Flag Lifecycle and the Five User Segments
To manage risk effectively, you cannot just "flip a switch" and be done with it. That would destroy the whole purpose of having control. We found using a ring-based deployment model in expanding user segments works well. Progressively, as a change matures, you enlarge the subset of users who see it. In other words, you limit the initial blast radius, gather data and feedback and only expand once certain of the stability and value of the change. Think of it as a funnel that gets bigger and bigger as the feature matures:

Your mileage may vary, but we found it effective for a feature to go through five consecutive segments. These build up on each other as every segment includes all users in the previous segments:
Development
The moment code is merged, it’s live in the development segment. This includes the developer environments and select internal environments used for testing and training. Here, the goal is immediate feedback for the developers themselves. It’s about seeing the code run in a safe environment without impact if anything goes sideways.
Validation
A feature moves to validation once it has been presented in a Sprint Review and the Product Owner has given the green light. Any bugs found during the initial dev phase must be resolved here. This is where you ensure the feature actually does what it was intended to do. The validation segment includes all internal testing and training environments.
Preview
Next, promote to preview. This opens the feature to select non-live tenants in production used for demos and manual or automated tests in production. This is a crucial step - it is now enabled on production infrastructure with production-like data, but without the risk of impacting a customer’s daily operations.
Release Candidate
Before a full release, we enter the release-candidate segment. This is the first time a change faces users who use the product for actual work. In our case, we use our own internal environments used by the whole company or a department. It’s the final "smoke test" to ensure that everything holds up under real-life usage and should happen a few days in advance of a release.
Release
Finally, a change reaches the release segment. It is enabled for all tenants in production. But even here, the work isn't "finished." You should keep the toggle active for a short period - this acts as an emergency brake. If something unexpected happens at scale, you can roll back the feature almost instantly without having to redeploy the entire application.
The below diagram depicts the 5 segments:

After all these segments, the feature flag is removed from the application code, including obsolete code and finally, the feature flag is removed as well. It is vital to remove the flag only after you do it in the application code. Otherwise, you will effectively disable the feature for all users.
Communication During the Lifecycle
A feature flag strategy is 50% technical and 50% communication. With changes, users expect information how these improve and impact their ways of using the software. This is not always necessary, many changes are self-explanatory and do what users expect. Nonetheless, more complex features require documentation and, even more so, breaking changes require clear explanation and instructions. Whatever the information and its form, timing is crucial. The effort you put into sending information or updating documentation will be lost, if you inform users about a new feature after it had been already deployed.
On the other hand, if you are informing ahead that you are about to release something new, then users expect to know when this happens - so you have to commit to a date. What if you don’t make it though? What if you must take care of multiple incidents in the meantime or your build infrastructure does not cooperate? All this can happen if your release process is complicated. Your final act of releasing should therefore be trivial to limit the amount of things that can break. We made it so, that releasing a feature is a single flip of a boolean. Like this, the risk of not meeting a communicated release date is minimal.
Furthermore, internal users and stakeholders will want to know about work in progress. The 5 segment strategy therefore guides when and how to provide information during each of the segments:
Development: Here communication is purely internal and within the team or closest stakeholders.
Validation: First tests have been successful so ask for feedback early. Provide an overview using mockups, slides or first working versions, e.g., in a Sprint Review, or live demos. Ask internal users to test and provide feedback.
Preview: Now that the feature has taken shape you should show the intention of releasing it. Update any "Coming Soon" section in docs, ask for final tests and make sure people outside your team use it. Release first versions of the documentation around internally and improve based on input.
Release Candidate/Release: Your feature is ready to step into the limelight. Update the official Changelog and documentation (either in app or separate). Make broad announcements using email, notifications or similar channels. Explain in a webinar or workshop. The particular communication strategy differs between features, but Changelog is the minimum.
A successful release strategy is 50% technical and 50% communication. Users expect to understand how changes will improve or impact their workflow. While many updates are self-explanatory, complex features require documentation, and breaking changes demand clear explanations and instructions.
Regardless of the format, timing is crucial. The effort spent on communication or documentation is wasted if users are frustrated by a new feature that has already appeared in their environment.
Managing Expectations and Risks
If you inform users ahead of time that a release is coming, you must commit to a date. However, real-world obstacles—like high-priority incidents or infrastructure failures—can jeopardize those commitments. To mitigate this, the act of "releasing" must be trivial. By utilizing feature flags, we have ensured that releasing a feature is as simple as flipping a boolean. This minimizes the risk of missing a communicated release date due to deployment complexities.
The 5-Segment Communication Strategy
Stakeholders and internal users need visibility into work-in-progress. Our five-segment strategy guides how and when to provide information:
Development: Communication remains internal to the team and immediate stakeholders.
Validation: Once initial tests succeed, seek early feedback. Provide overviews via mockups, slides, or early working versions (e.g., during Sprint Reviews or live demos). Encourage internal users to test and provide input.
Preview: As the feature takes its final shape, signal the intent to release. Update "Coming Soon" documentation, request final testing from users outside the core team, and refine internal documentation based on their feedback.
Release Candidate & Release: Your feature is ready for the limelight. Update the official Changelog and external documentation. Use broad channels - such as email, in-app notifications, or webinars - to accompany the rollout. Communicate ahead or together with the release. While the specific strategy varies by feature, an entry in the Changelog is the minimum requirement. Important: Don’t skip the release candidate segment, we have had cases of critical bugs being found a day before planned rollout.
Managing Complexity
Feature flags do not come free. You need to branch at the appropriate places in code to ensure your unfinished changes do not leak. That can get out of hand quickly without a plan. If you are dealing with quantities of unreleased features, this strategy is likely not for you. Too many flags is deadly both for codebases and velocity, they must be short-lived. Removing a flag should be included in the definition of done for features, but you also need to ensure there are not too many features in progress to begin with.
If you are already at the point of having too many flags, a viable strategy is to allow adding a new feature flag only if two existing ones have been removed. This way you pay off technical debt gradually.
Another problem is if flags in your flag management tool drift away from the codebase. Then you have to go through each and every one to resolve the issue. There is a better way though - let the compiler do the work! Instead of:
if (featureState["fresh-design"]?.enabled || false) {
// new logic
} else {
...
}
Generate an enum from flag definitions. You will get compilation errors unless the flag exists:
// start generated
enum class BoolFeatureFlags(val key: String) {
FRESH_DESIGN("fresh-design"),
V2_API("v2-api"),
...
}
// end generated
if (featureState.enabled(FRESH_DESIGN)) {
// new logic
} else {
...
}
The workflow then looks something like this:

Each flag management tool has a different format, so your mileage may vary, but a simple script will do the job. That way, you prevent different sets of feature flags. If you want to go the extra mile, an idea courtesy of Marc, is to have the code generator skip flags that are marked as deprecated, e.g., with a description prefix [DEPRECATED]. Then you can very safely remove flags like so:
Add
[DEPRECATED]to description in the flag management tool. This triggers compiler errors in all places the flag is used.Update code to remove unneeded logic.
Deploy the application.
Then delete the flag from the management tool.
With this in place, your codebase won’t suffer.
Conclusion
Segmenting your users in up to five categories and consecutively enabling new changes one by one in each using feature flags gives you full control over the feature release process. By adhering to this rollout strategy, our team achieves:
Higher and fully automated deployment frequency using Continuous Deployment.
Lower failure rate. Bugs are caught in early segments.
Better product-market fit. Features get very early feedback, even from customers.
The trade-off for this strategy lies in higher code complexity. It is, however, well-manageable if you 1) focus on finishing features, 2) define a clear feature flag lifecycle, and 3) deeply integrate feature flag management with your codebase.
Let me know if you try or adapt this blueprint to your needs, looking forward to your thoughts!
