Feature flags decouple deployment from release enabling runtime feature control without code changes. Implementation levels: simple boolean flags, percentage rollouts, user targeting, multivariate flags.
Basic feature flag implementation:
```javascript
// Simple in-memory flags
class FeatureFlags {
constructor() {
this.flags = new Map();
}
setFlag(name, enabled) {
this.flags.set(name, enabled);
}
isEnabled(name, defaultValue = false) {
return this.flags.get(name) ?? defaultValue;
}
}
const flags = new FeatureFlags();
// Usage
if (flags.isEnabled('new-checkout')) {
return newCheckoutFlow();
} else {
return oldCheckoutFlow();
}
```
Advanced flags with user targeting:
```javascript
class FeatureFlagService {
constructor(config) {
this.config = config;
}
isEnabled(flagName, context = {}) {
const flag = this.config.flags[flagName];
if (!flag) return false;
// Check if globally enabled
if (flag.enabled === false) return false;
// Check user targeting
if (flag.targeting) {
return this.evaluateTargeting(flag.targeting, context);
}
// Check percentage rollout
if (flag.rolloutPercentage !== undefined) {
return this.evaluatePercentage(
flagName,
context.userId,
flag.rolloutPercentage
);
}
return flag.enabled;
}
evaluateTargeting(rules, context) {
// User ID targeting
if (rules.userIds && context.userId) {
if (rules.userIds.includes(context.userId)) {
return true;
}
}
// Email domain targeting
if (rules.emailDomains && context.email) {
const domain = context.email.split('@')[1];
if (rules.emailDomains.includes(domain)) {
return true;
}
}
// Custom attributes
if (rules.attributes) {
return Object.entries(rules.attributes).every(([key, value]) => {
return context[key] === value;
});
}
return false;
}
evaluatePercentage(flagName, userId, percentage) {
// Consistent hashing for same user
const hash = this.hashCode(flagName + userId);
const bucket = Math.abs(hash) % 100;
return bucket < percentage;
}
hashCode(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash;
}
}
// Configuration
const config = {
flags: {
'new-checkout': {
enabled: true,
rolloutPercentage: 25,
targeting: {
emailDomains: ['company.com'],
userIds: ['beta-user-1', 'beta-user-2']
}
},
'premium-features': {
enabled: true,
targeting: {
attributes: {
plan: 'premium'
}
}
}
}
};
const flagService = new FeatureFlagService(config);
// Usage with context
const user = {
userId: '12345',
email: 'user@company.com',
plan: 'premium'
};
if (flagService.isEnabled('new-checkout', user)) {
// Show new checkout
}
```
LaunchDarkly integration:
```javascript
const LaunchDarkly = require('launchdarkly-node-server-sdk');
const ldClient = LaunchDarkly.init('YOUR_SDK_KEY');
await ldClient.waitForInitialization();
const user = {
key: 'user-key-123',
email: 'user@example.com',
custom: {
plan: 'premium',
signupDate: '2024-01-01'
}
};
// Boolean flag
const showNewFeature = await ldClient.variation(
'new-feature',
user,
false // default
);
// Multivariate flag
const buttonColor = await ldClient.variation(
'button-color',
user,
'blue' // default
);
// JSON flag for complex configuration
const config = await ldClient.variation(
'app-config',
user,
{ theme: 'light', maxItems: 10 }
);
```
A/B testing with feature flags:
```javascript
class ABTestingService {
constructor(flagService, analyticsService) {
this.flags = flagService;
this.analytics = analyticsService;
}
getVariant(experimentName, context) {
const flag = this.flags.getFlag(experimentName);
if (!flag || !flag.experiment) {
return 'control';
}
// Assign variant based on user
const variant = this.assignVariant(
experimentName,
context.userId,
flag.experiment.variants
);
// Track assignment
this.analytics.track('Experiment Viewed', {
experimentName,
variant,
userId: context.userId
});
return variant;
}
assignVariant(experimentName, userId, variants) {
const hash = this.hashCode(experimentName + userId);
const bucket = Math.abs(hash) % 100;
let cumulative = 0;
for (const [variant, percentage] of Object.entries(variants)) {
cumulative += percentage;
if (bucket < cumulative) {
return variant;
}
}
return 'control';
}
trackConversion(experimentName, context, metric, value) {
const variant = this.getVariant(experimentName, context);
this.analytics.track('Experiment Conversion', {
experimentName,
variant,
metric,
value,
userId: context.userId
});
}
}
// Usage
const variant = abTesting.getVariant('checkout-redesign', { userId: '123' });
if (variant === 'treatment') {
showNewCheckout();
} else {
showOldCheckout();
}
// Track conversion
abTesting.trackConversion(
'checkout-redesign',
{ userId: '123' },
'purchase',
99.99
);
```
Flag lifecycle management:
```javascript
class FlagLifecycleManager {
constructor() {
this.flags = new Map();
}
createFlag(name, config) {
this.flags.set(name, {
...config,
createdAt: new Date(),
status: 'pending',
rolloutHistory: []
});
}
updateRollout(name, percentage) {
const flag = this.flags.get(name);
flag.rolloutHistory.push({
percentage,
timestamp: new Date()
});
flag.rolloutPercentage = percentage;
if (percentage === 100) {
flag.status = 'complete';
}
}
retireFlag(name) {
const flag = this.flags.get(name);
flag.status = 'retired';
flag.retiredAt = new Date();
}
getOldFlags(daysOld = 90) {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - daysOld);
return Array.from(this.flags.entries())
.filter(([name, flag]) => {
return flag.status === 'complete' &&
flag.rolloutHistory.length > 0 &&
flag.rolloutHistory[flag.rolloutHistory.length - 1].timestamp < cutoff;
})
.map(([name]) => name);
}
}
```
CI/CD integration:
```yaml
deploy:
stage: deploy
script:
# Deploy with flag disabled
- kubectl apply -f k8s/
# Enable flag for internal users
- |
curl -X PATCH https://app.launchdarkly.com/api/v2/flags/my-project/new-feature \
-H "Authorization: $LD_API_KEY" \
-d '{
"instructions": [{
"kind": "updateFallthroughVariationOrRollout",
"rollout": {
"variations": [
{"variation": 0, "weight": 95000},
{"variation": 1, "weight": 5000}
]
}
}]
}'
# Monitor metrics
- ./monitor-rollout.sh new-feature 5m
# Gradually increase
- ./update-flag-percentage.sh new-feature 25
```
Best practices: use descriptive flag names, document flag purpose and owner, implement flag cleanup process (retire old flags), separate long-term operational flags from temporary release flags, use targeting for staged rollouts, implement audit logging for flag changes, test both flag states, avoid flag dependencies (flags depending on other flags), use SDK with caching for performance, implement flag evaluation metrics. Understanding feature flags enables safe, gradual feature releases with instant kill switch capability.