Skip to Content
PlatformBest Practices

Best Practices

Follow these best practices to implement feature flags effectively and avoid common pitfalls. These guidelines will help you build a robust feature flag system that scales with your application.

Feature Flag Naming

Use Descriptive Names

// ✅ Good naming 'new-checkout-flow' 'beta-features' 'admin-panel-v2' 'pricing-page-redesign' // ❌ Avoid 'flag1' 'new_feature' 'TEST_FLAG' 'feature-123'

Use Consistent Patterns

// Group related flags with prefixes 'checkout.new-flow' 'checkout.express-payment' 'checkout.guest-checkout' // Use prefixes for different types 'exp.new-ui' // Experiment 'config.max-retries' // Configuration 'kill.emergency-disable' // Kill switch 'beta.new-features' // Beta features

Avoid Environment-Specific Names

// ❌ Don't include environment in flag names 'new-feature-dev' 'feature-prod' 'staging-flag' // ✅ Use environment-agnostic names 'new-feature' 'feature-flag' 'experiment-flag'

Flag Lifecycle Management

Define Clear Lifecycles

// Flag lifecycle stages const flagLifecycle = { development: { purpose: 'Local development and testing', duration: '1-2 weeks', cleanup: 'Remove after development', }, testing: { purpose: 'Internal testing and QA', duration: '1-2 weeks', cleanup: 'Remove after testing', }, rollout: { purpose: 'Gradual production rollout', duration: '1-4 weeks', cleanup: 'Remove after full rollout', }, permanent: { purpose: 'Long-term configuration', duration: 'Ongoing', cleanup: 'Review quarterly', }, }

Set Expiration Dates

// Always set expiration dates for temporary flags const flagConfig = { name: 'new-checkout', expirationDate: '2024-03-01', reminderDate: '2024-02-15', owner: '[email protected]', }

Regular Cleanup

// Automated cleanup process const cleanupFlags = async () => { const flags = await df.getAllFeatures() const expiredFlags = flags.filter( flag => new Date(flag.expirationDate) < new Date(), ) for (const flag of expiredFlags) { console.log(`Flag ${flag.key} has expired`) // Send reminder to owner await sendCleanupReminder(flag) } }

Targeting Best Practices

Use Specific Targeting

// ✅ Specific targeting const specificTargeting = { rules: [ { attribute: 'user.plan', operator: 'equals', value: 'premium', }, { attribute: 'user.country', operator: 'in', value: ['US', 'CA'], }, ], } // ❌ Too broad targeting const broadTargeting = { rules: [ { attribute: 'user.email', operator: 'contains', value: '@', }, ], }

Test Targeting Rules

// Test targeting with different user contexts const testTargeting = async (featureKey, targetingRules) => { const testUsers = [ { userId: '1', plan: 'premium', country: 'US' }, { userId: '2', plan: 'free', country: 'CA' }, { userId: '3', plan: 'premium', country: 'UK' }, ] for (const user of testUsers) { const result = await df.evaluateTargeting(featureKey, user, targetingRules) console.log(`User ${user.userId}: ${result.enabled}`) } }

Gradual Rollouts

// Start with small percentages const gradualRollout = { phase1: { percentage: 1, duration: '1 day' }, phase2: { percentage: 5, duration: '3 days' }, phase3: { percentage: 25, duration: '1 week' }, phase4: { percentage: 50, duration: '1 week' }, phase5: { percentage: 100, duration: '1 week' }, }

Performance Optimization

Efficient Flag Evaluation

// Use batch evaluation for multiple flags const evaluateMultipleFlags = async user => { const flags = await df.evaluateMultiple( ['feature-a', 'feature-b', 'feature-c'], user, ) return flags } // Cache flag results appropriately const cachedEvaluation = (() => { const cache = new Map() return async (featureKey, user) => { const cacheKey = `${featureKey}-${user.userId}` if (cache.has(cacheKey)) { return cache.get(cacheKey) } const result = await df.isFeatureEnabled(featureKey, user) cache.set(cacheKey, result) // Clear cache after 5 minutes setTimeout(() => cache.delete(cacheKey), 300000) return result } })()

Minimize Network Calls

// Initialize SDK with proper caching const df = new DarkFeature('your-key', { cacheTimeout: 300000, // 5 minutes maxCacheSize: 1000, enableBackgroundSync: true, }) // Wait for initialization await df.waitForInitialization()

Security Considerations

Client-Side vs Server-Side

// Client-side (public key) const clientDf = new DarkFeature('public-client-key') // Can only read flags, cannot modify // Server-side (private key) const serverDf = new DarkFeature('private-server-key') // Full access to all operations

User Context Security

// ✅ Safe user context const safeUserContext = { userId: '123', email: '[email protected]', plan: 'premium', country: 'US', } // ❌ Don't include sensitive data const unsafeUserContext = { userId: '123', email: '[email protected]', password: 'secret', // Never include passwords creditCard: '1234-5678-9012-3456', // Never include PII ssn: '123-45-6789', // Never include sensitive data }

Environment Isolation

// Ensure environment isolation const ensureEnvironmentIsolation = () => { const currentEnv = process.env.NODE_ENV if (currentEnv === 'production') { // Never enable debug features in production if (process.env.DEBUG_MODE === 'true') { throw new Error('Debug mode cannot be enabled in production') } } }

Code Organization

Centralized Flag Management

// Create a centralized flag service class FeatureFlagService { constructor() { this.df = new DarkFeature('your-key') } async isFeatureEnabled(featureKey, user, defaultValue = false) { try { return await this.df.isFeatureEnabled(featureKey, user) } catch (error) { console.error(`Error evaluating flag ${featureKey}:`, error) return defaultValue } } async getFeatureVariation(featureKey, user, defaultValue = null) { try { return await this.df.getFeatureVariation(featureKey, user) } catch (error) { console.error(`Error getting variation for ${featureKey}:`, error) return defaultValue } } } // Use the service const featureFlags = new FeatureFlagService() const isEnabled = await featureFlags.isFeatureEnabled('new-feature', user)

Flag Constants

// Define flag constants const FEATURE_FLAGS = { NEW_CHECKOUT: 'new-checkout-flow', BETA_FEATURES: 'beta-features', ADMIN_PANEL_V2: 'admin-panel-v2', PRICING_TEST: 'pricing-page-test' } as const // Use constants const isEnabled = await df.isFeatureEnabled(FEATURE_FLAGS.NEW_CHECKOUT, user)

Type Safety

// TypeScript definitions interface FeatureFlag { key: string enabled: boolean defaultValue: boolean targeting?: TargetingRules } interface User { userId: string email: string plan: string country: string custom?: Record<string, any> } // Type-safe flag evaluation async function isFeatureEnabled( featureKey: keyof typeof FEATURE_FLAGS, user: User, ): Promise<boolean> { return await df.isFeatureEnabled(FEATURE_FLAGS[featureKey], user) }

Testing Strategies

Unit Testing

// Test flag evaluation logic describe('Feature Flag Evaluation', () => { it('should enable feature for premium users', async () => { const user = { userId: '123', plan: 'premium', country: 'US', } const isEnabled = await df.isFeatureEnabled('premium-feature', user) expect(isEnabled).toBe(true) }) it('should disable feature for free users', async () => { const user = { userId: '456', plan: 'free', country: 'US', } const isEnabled = await df.isFeatureEnabled('premium-feature', user) expect(isEnabled).toBe(false) }) })

Integration Testing

// Test with real SDK describe('DarkFeature SDK Integration', () => { let df beforeEach(async () => { df = new DarkFeature('test-key') await df.waitForInitialization() }) it('should evaluate flags correctly', async () => { const user = { userId: 'test-user' } const result = await df.isFeatureEnabled('test-flag', user) expect(typeof result).toBe('boolean') }) })

End-to-End Testing

// Test complete user flows describe('Feature Flag User Flows', () => { it('should show new checkout for enabled users', async () => { // Setup user with feature enabled const user = createUserWithFeature('new-checkout', true) // Navigate to checkout await page.goto('/checkout') // Verify new checkout is shown await expect(page.locator('.new-checkout')).toBeVisible() }) })

Monitoring and Alerting

Flag Usage Monitoring

// Monitor flag usage const monitorFlagUsage = async () => { const flags = await df.getAllFeatures() for (const flag of flags) { const usage = await df.getFlagUsage(flag.key) if (usage.evaluationCount === 0) { console.warn(`Flag ${flag.key} has not been evaluated`) } if (usage.lastEvaluated > 7 * 24 * 60 * 60 * 1000) { // 7 days console.warn(`Flag ${flag.key} has not been evaluated recently`) } } }

Performance Monitoring

// Monitor flag evaluation performance const monitorPerformance = async () => { const metrics = await df.getPerformanceMetrics() if (metrics.averageEvaluationTime > 100) { // 100ms console.warn('Flag evaluation is taking too long') } if (metrics.errorRate > 0.01) { // 1% console.error('Flag evaluation error rate is high') } }

Alerting Setup

// Set up alerts for critical issues const setupAlerts = async () => { await df.setupAlert({ type: 'flag-not-evaluated', threshold: 24 * 60 * 60 * 1000, // 24 hours action: 'email', }) await df.setupAlert({ type: 'high-error-rate', threshold: 0.05, // 5% action: 'slack', }) }

Documentation

Flag Documentation

// Document each flag const flagDocumentation = { 'new-checkout': { description: 'New checkout flow with improved UX', owner: '[email protected]', created: '2024-01-15', expectedDuration: '2 weeks', businessCase: 'Improve conversion rate by 10%', technicalDetails: 'Uses new React components', rollbackPlan: 'Switch back to old checkout if issues arise', }, }

Team Guidelines

// Team guidelines document const teamGuidelines = { naming: 'Use kebab-case for flag names', targeting: 'Always test targeting rules before deployment', cleanup: 'Set expiration dates for all temporary flags', monitoring: 'Monitor flag usage and performance', documentation: 'Document business case and technical details', }

Common Anti-Patterns

Don’t Use Flags for Everything

// ❌ Don't use flags for everything if (isFeatureEnabled('show-button')) { return <Button /> } // ✅ Use flags for meaningful features if (isFeatureEnabled('new-checkout-flow')) { return <NewCheckout /> } else { return <OldCheckout /> }

Don’t Nest Flags Deeply

// ❌ Don't nest flags deeply if (isFeatureEnabled('feature-a')) { if (isFeatureEnabled('feature-b')) { if (isFeatureEnabled('feature-c')) { // Complex nested logic } } } // ✅ Use a single flag for complex features if (isFeatureEnabled('advanced-feature-set')) { // Handle all advanced features together }

Don’t Forget Cleanup

// ❌ Don't leave flags forever if (isFeatureEnabled('old-feature')) { // This will never be cleaned up } // ✅ Set expiration and cleanup const flagConfig = { name: 'temporary-feature', expirationDate: '2024-03-01', cleanupPlan: 'Remove after full rollout', }

Next Steps

Last updated on