This document outlines Bayat's standards and best practices for implementing and managing feature flags across all platforms and project types.
- Introduction
- Feature Flag Types
- Implementation Patterns
- Naming and Organization
- Lifecycle Management
- Testing Strategies
- Monitoring and Observability
- Security Considerations
- Platform-Specific Implementation
- Tools and Libraries
- Integration with CI/CD
- Governance and Documentation
Feature flags (also known as feature toggles or feature switches) are a powerful technique that enables teams to modify system behavior without changing code. They facilitate:
- Continuous delivery and deployment
- A/B testing and experimentation
- Progressive rollouts to users
- Operational control (kill switches)
- Managing technical debt
This document provides comprehensive standards for implementing feature flags consistently across Bayat projects.
Used to enable/disable features that are under active development.
- Purpose: Hide incomplete features in production
- Lifespan: Short-term (removed after feature is stable)
- Scope: Usually binary (on/off)
- Decision Point: Build-time or startup for simple cases, runtime for more flexibility
Used to control operational aspects of the system in production.
- Purpose: Emergency shutoff, load management, maintenance mode
- Lifespan: Long-term or permanent
- Scope: Binary (on/off) or multi-variate
- Decision Point: Runtime, often dynamically updated
Used for A/B testing, multivariate testing, or experimentation.
- Purpose: Test variations of a feature with different user segments
- Lifespan: Medium-term (duration of experiment)
- Scope: Multi-variate (multiple variations)
- Decision Point: Runtime, dynamically determined per user/session
Used to control feature access for specific users or user segments.
- Purpose: Premium features, early access, beta programs
- Lifespan: Long-term or permanent
- Scope: Binary (on/off) or multi-variate
- Decision Point: Runtime, determined by user attributes
Feature flags should follow a consistent structure:
Feature Flag = {
key: String, // Unique identifier
name: String, // Human-readable name
description: String, // Purpose and expected behavior
type: Enum, // Release, Operational, Experiment, Permission
enabled: Boolean, // Default state
variants: Array<Variant>, // For multi-variate flags
conditions: Object, // Rules for targeting
owner: String, // Team/individual responsible
createdAt: DateTime, // Creation timestamp
expireAt: DateTime, // Optional planned removal date
}
-
Simple Boolean Toggle:
if (featureFlags.isEnabled('new-search-algorithm')) { // Use new algorithm } else { // Use old algorithm }
-
Multi-Variant Selection:
const variant = featureFlags.getVariant('pricing-model', user); switch (variant) { case 'control': return standardPricing(); case 'experiment-a': return discountedPricing(); case 'experiment-b': return tieredPricing(); default: return standardPricing(); }
-
Targeting Rules:
// Pseudocode for flag configuration { key: 'premium-feature', conditions: { operator: 'ALL', rules: [ { attribute: 'subscription', operator: 'EQUALS', value: 'premium' }, { attribute: 'region', operator: 'IN', values: ['US', 'EU'] } ] } }
-
Gradual Rollout:
// Pseudocode for flag configuration { key: 'new-ui', rollout: { percentage: 25, attribute: 'userId' // Attribute used for consistent bucketing } }
-
Centralized vs. Distributed:
- Centralized: A single source of truth for all feature flags
- Distributed: Flags defined close to their usage
-
Storage Options:
- Configuration files (for simple, static flags)
- Database (for dynamic, runtime-updatable flags)
- Specialized feature flag services
-
Evaluation Location:
- Backend evaluation (more secure, consistent)
- Frontend evaluation (more responsive, less load on backend)
- Hybrid approach (initial load from backend, caching on frontend)
-
Performance Considerations:
- Cache flag configuration to avoid repeated lookups
- Implement fallback mechanisms for service unavailability
- Consider flag evaluation latency in critical paths
-
Key Format: Use kebab-case for flag keys:
new-checkout-flow
enhanced-search-algorithm
premium-user-dashboard
-
Namespace Structure:
[product].[feature].[subfeature]
for large organizations with multiple products[feature].[subfeature]
for single-product teams
-
Prefixing for Purpose:
release:
for release flagsops:
for operational flagsexp:
for experiment flagsperm:
for permission flags
-
Group by Domain/Feature:
- Group related flags together within a domain or feature area
- e.g.,
search.algorithm
,search.ui
,search.filters
-
Group by Team Ownership:
- Organize flags by the team responsible for them
- Helps with lifecycle management and accountability
-
Group by Life Expectancy:
- Temporary vs. permanent flags
- Helps with technical debt management
-
Creation Request:
- Specify purpose, expected behavior, target audience
- Define success metrics and evaluation criteria
- Document testing approach
-
Implementation:
- Add flag to codebase with default off (for new features)
- Update documentation
- Create tracking ticket for removal (for temporary flags)
-
Activation:
- Test functionality with flag on and off
- Implement gradual rollout if needed
- Monitor for issues after activation
-
Identification:
- Regularly review active flags
- Flag candidates for removal: fully deployed features, completed experiments
-
Cleanup:
- Remove conditional code
- Remove flag configuration
- Update documentation
-
Verification:
- Verify functionality after flag removal
- Update tests to reflect permanent state
-
Flag Expiration Dates:
- Set planned removal dates for temporary flags
- Regular audits of active flags
-
Ownership Assignment:
- Clearly assign ownership for each flag
- Make flag cleanup part of feature completion
-
Regular Cleanup Sprints:
- Dedicate time periodically for flag cleanup
- Track flag debt metrics
-
Test Combinations:
- Test all flag combinations for critical paths
- Test default on and off states for all flags
-
Parameterized Tests:
- Run tests with different flag configurations
- Identify critical flag combinations
-
Integration Tests:
- Test interaction between multiple flags
- Verify no unexpected side effects
-
Environment-Specific Configurations:
- Default configs for different environments
- Dev/staging environments with easy flag toggling
-
Testing Tools:
- UI for toggling flags in test environments
- API endpoints for automation
-
Override Mechanisms:
- Query parameter overrides for testing
- User-specific overrides for QA
-
Flag Usage Metrics:
- Which flags are being evaluated and how often
- Which code paths are being executed
-
Performance Impact:
- Latency introduced by flag evaluation
- System performance with different flag configurations
-
Business Metrics:
- Conversion rates, engagement, revenue impact
- Experiment outcomes
-
Flag Decision Logging:
- Log all flag evaluations with context
- Include user attributes that influenced decision
-
Distributed Tracing:
- Include flag decisions in trace context
- Track flag impact across services
-
Debugging Support:
- Tools to view active flags for a session/user
- Ability to override flags for troubleshooting
-
Role-Based Access:
- Admin: Create/delete flags, modify production
- Developer: Create/delete flags, modify non-production
- Viewer: View flag configurations
-
Environment Restrictions:
- Controlled promotion between environments
- Approval workflows for production changes
-
Audit Trail:
- Log all flag configuration changes
- Record who made changes and when
-
Sensitive Data Handling:
- Don't use PII in flag rules without proper controls
- Encrypt sensitive targeting attributes
-
Privacy Compliance:
- Consider GDPR/CCPA implications of user targeting
- Implement appropriate consent mechanisms
// React example with a feature flag hook
function useFeatureFlag(flagKey, defaultValue = false) {
const [enabled, setEnabled] = useState(defaultValue);
useEffect(() => {
// Initial load
const isEnabled = featureFlags.isEnabled(flagKey);
setEnabled(isEnabled);
// Subscribe to changes
const unsubscribe = featureFlags.subscribe(flagKey, (newValue) => {
setEnabled(newValue);
});
return () => unsubscribe();
}, [flagKey]);
return enabled;
}
// Usage
function SearchComponent() {
const newAlgorithmEnabled = useFeatureFlag('new-search-algorithm');
return (
<div>
{newAlgorithmEnabled ? <NewSearchUI /> : <LegacySearchUI />}
</div>
);
}
-
Native iOS (Swift):
// Swift example if FeatureFlags.shared.isEnabled("new-profile-ui") { showNewProfileUI() } else { showLegacyProfileUI() }
-
Native Android (Kotlin):
// Kotlin example if (featureFlags.isEnabled("new-profile-ui")) { showNewProfileUI() } else { showLegacyProfileUI() }
-
React Native:
// React Native example
const NewFeature = () => {
const featureEnabled = useFeatureFlag('new-feature');
if (!featureEnabled) return null;
return (
<View>
<Text>New Feature UI</Text>
</View>
);
};
-
Java/Spring:
@Service public class PaymentService { private final FeatureFlagService featureFlagService; @Autowired public PaymentService(FeatureFlagService featureFlagService) { this.featureFlagService = featureFlagService; } public ProcessResult processPayment(Payment payment, User user) { if (featureFlagService.isEnabled("new-payment-processor", user)) { return newPaymentProcessor.process(payment); } else { return legacyPaymentProcessor.process(payment); } } }
-
Node.js:
// Express middleware for feature flags
function featureFlagMiddleware(req, res, next) {
req.featureFlags = {};
// Evaluate all relevant flags for this user
req.featureFlags.newDashboard = featureFlags.isEnabled('new-dashboard', {
userId: req.user?.id,
userRole: req.user?.role,
region: req.geo?.region
});
next();
}
// Usage in route
app.get('/dashboard', featureFlagMiddleware, (req, res) => {
if (req.featureFlags.newDashboard) {
return res.render('new-dashboard');
}
return res.render('dashboard');
});
-
Unleash:
- Open-source feature flag platform
- Supports all flag types and complex rules
- Self-hosted with client libraries for multiple languages
-
Flagsmith:
- Open-source feature flag management
- Strong support for user segmentation
- Available as self-hosted or SaaS
-
LaunchDarkly:
- Enterprise feature flag management
- Comprehensive targeting and experimentation
- Robust SDKs for all major platforms
-
Split:
- Feature delivery platform
- Strong analytics and experimentation
- Enterprise-grade security and compliance
-
Optimizely:
- Experimentation focused
- Statistical analysis built in
- Strong enterprise support
For projects with simpler needs, consider:
-
Database-backed solution:
- Store flags in database with caching
- Simple admin UI for management
- Basic targeting capabilities
-
Configuration-based solution:
- YAML/JSON configuration files
- Environment-specific overrides
- CI/CD pipeline integration
-
Feature Branch Development:
- Develop behind feature flags in trunk
- CI tests feature with flag on and off
-
Deployment Process:
- Deploy code with flags off
- Enable flags after deployment verification
- Progressive rollout via flag targeting
-
Automated Testing:
- Test matrix with different flag combinations
- Performance tests with critical flags
-
Dark Launches:
- Deploy code behind flag (off)
- Enable for internal users
- Gradually roll out to production
-
Canary Releases:
- Enable feature for small percentage
- Monitor for issues
- Gradually increase percentage
-
Ring-Based Deployment:
- Inner ring: Internal users
- Middle ring: Beta users
- Outer ring: All users
Maintain a central inventory of all feature flags:
-
Required Metadata:
- Key, name, description
- Type and purpose
- Owner and creation date
- Expected lifetime
- Current status
-
Documentation Location:
- Centralized wiki/knowledge base
- Service/component documentation
- Within flag management system
-
Flag Review Process:
- Quarterly review of all active flags
- Identify candidates for removal
- Update documentation
-
Metrics Review:
- Flag usage statistics
- Performance impact
- Technical debt assessment
-
Cleanup Planning:
- Prioritize flag cleanup tasks
- Schedule cleanup work
- Track cleanup progress
-
Do:
- Use feature flags for all significant changes
- Set expiration dates for temporary flags
- Test with flags in both states
- Document all flags and their purpose
-
Don't:
- Nest feature flags (avoid flag dependencies)
- Use flags for configuration that rarely changes
- Leave obsolete flags in the codebase
- Create overly complex targeting rules