By Arjun Mehta
You've shipped a feature. It looks great in staging. But in production, it breaks for 5% of users. Now you're rolling back. Customers are confused. Your velocity tanks while you debug.
Feature flags solve this problem. A feature flag is a simple switch that lets you turn a feature on or off without deploying new code. Instead of deploying a feature to everyone immediately, you can:
- Deploy to 1% of users first
- Monitor for issues
- Gradually roll out to 100%
- Kill the feature instantly if problems appear
Feature flags aren't just for safer releases. They're for faster releases, A/B testing, gradual rollouts, and managing feature complexity in your codebase.
This guide explains how to use feature flags effectively without creating technical debt.
Types of Feature Flags
Release Flags
Controlled rollout of new features. Deploy code to production with the feature hidden. Gradually expose it.
Example:
if feature_flag.is_enabled('new_checkout_flow', user_id):
return new_checkout_flow(user)
else:
return old_checkout_flow(user)
You deploy with the flag off. Then:
- Enable for 1% of users
- Monitor for errors and performance
- If good, enable for 10%
- Keep ramping until 100%
- Remove the flag after a week
Benefits: Safe rollouts. If something breaks, disable the flag and instantly fix it for 99% of users.
Experiment Flags
Run A/B tests. Show version A to some users, version B to others. Measure which converts better.
Example:
if feature_flag.variant('checkout_button_color', user_id) == 'red':
button_color = 'red'
else:
button_color = 'blue'
Benefits: Test changes before committing. Make data-driven decisions.
Ops Flags
Control operational behavior. Disable expensive features when the system is under load. Enable debug logging in production for specific users.
Example:
if feature_flag.is_enabled('expensive_recommendation_engine', user_id):
recommendations = expensive_ml_model.predict(user)
else:
recommendations = cheap_cache_lookup(user)
Benefits: Control system behavior without deploying. Respond to load spikes instantly.
Permission Flags
Gate features to specific users. Beta features for early adopters. Premium features for paying customers.
Example:
if feature_flag.has_permission('advanced_analytics', user.account_type):
show_advanced_analytics()
else:
show_basic_analytics()
Best Practices
1. Keep Flag Logic Simple
Bad:
if (feature_flag.is_enabled('feature_x') and
user.subscription_level >= 2 and
user.created_date < datetime(2025, 1, 1) and
request.path not in ['/admin', '/settings']):
use_new_logic()
else:
use_old_logic()
Good:
if feature_flag.should_use_new_feature(user, request):
use_new_logic()
else:
use_old_logic()
The decision logic should be in the flag service, not scattered through your code.
2. Clean Up Flags
Flags are temporary. Once a feature is fully rolled out or an experiment ends, remove the flag. Old flags are technical debt.
Create a process:
- When a flag is 100% enabled for a week, mark it for cleanup
- Remove the old code path
- Delete the flag
If you don't clean up, you accumulate dead code paths and confusing logic.
3. Make Flag Configuration Easy
Don't hardcode flag values in your application. Use a service that lets you change flags without deploying:
class FeatureFlagService:
def is_enabled(self, flag_name, user_id):
# Fetch from central service
config = self.fetch_from_service(flag_name)
if config['type'] == 'percentage':
return hash(user_id) % 100 < config['percentage']
elif config['type'] == 'user_list':
return user_id in config['user_ids']
return False
Now you can change rollout percentages, add users to beta groups, all without deploying.
4. Monitor Flag Changes
When you enable a flag for 10% of users and error rate spikes, you need to know it was the flag that changed. Log all flag changes and correlation with metrics.
5. Use Consistent User IDs
When you decide to show variant A to user 123, they should always see variant A. Use consistent hashing:
def get_variant(flag_name, user_id):
hash_value = hash(f"{flag_name}_{user_id}") % 100
if hash_value < 50:
return 'variant_a'
else:
return 'variant_b'
Same user always gets same variant. No flaky experiments.
Common Pitfalls
Flag Proliferation
If every feature gets a flag, you'll have hundreds of flags. Too many flags is confusing and slows down development.
Solution: Use flags for:
- Risky changes
- A/B tests
- Gradual rollouts
- Ops decisions
Don't use flags for every small feature. Some features can just deploy.
Stale Flags
Flags from experiments 6 months ago that are still in the code. Dead code paths that no one understands.
Solution: Set expiration dates. Flags older than 30 days trigger alerts. Force removal if not actively used.
Flag Logic Creep
Feature flags start simple. Then you add conditions: only for paying users, only in US, except for accounts created before 2024. Eventually flag logic is incomprehensible.
Solution: Keep flag logic simple. If you need complex rules, move the rules to the flag service.
Performance Impact
Every feature flag check is a tiny performance hit. With hundreds of flags, it adds up.
Solution:
- Cache flag configurations locally
- Use efficient flag services (don't make network calls for every request)
- Profile to measure impact
- Remove old flags
Tools for Feature Flags
Open Source:
- LaunchDarkly: Feature flag service
- Unleash: Open-source feature flag platform
- OpenFeature: Standard interface for feature flags
Managed Services:
- LaunchDarkly: Comprehensive, well-designed
- Amplitude: Analytics and experimentation
- Optimizely: Experimentation and personalization
DIY: You can build basic flag service with a database table:
CREATE TABLE feature_flags (
id INT PRIMARY KEY,
name VARCHAR(255),
enabled BOOLEAN,
rollout_percentage INT,
created_at TIMESTAMP
);
For simple use cases, this is enough. As you scale, a managed service might make sense.
Feature Flags and Codebase Health
Feature flags can hide architectural issues. You have old logic and new logic coexisting. If the flag logic gets messy, your code gets messy.
Tools like Glue can help identify where flags are creating complexity and coupling. Flag logic should be isolated and easy to understand.
Frequently Asked Questions
Q: Do feature flags slow down my application?
A: Slightly. Each flag check is a tiny performance cost. But the benefit (safe deployments) usually outweighs the cost. Profile your specific application. For most applications, the impact is negligible.
Q: Should I use feature flags for everything?
A: No. Use them for risky changes, experiments, and operational control. Small features can just deploy. Too many flags create complexity.
Q: How long should I keep a flag in my code?
A: Remove it within a few weeks. Once a feature is stable (running for a week with no issues), remove the flag and the old code path. Old flags are technical debt.
Q: Can I use feature flags for permissions and access control?
A: You can, but it's not ideal for security-critical decisions. Feature flags are typically about features, not security. For access control, use proper authorization systems.
Q: How do I handle feature flags in my database migrations?
A: Treat database changes specially. If you deploy code with a new column and the flag is off, but an old version of the code reads from the database, it'll fail. For database changes, deploy the change first, wait for all servers to be running new code, then enable the flag.