
Introduction
Shipping new features can be risky, especially when they affect critical user flows like checkout, authentication, or payment processing. Feature flagging helps you release safely by turning features on or off without redeploying your application. Companies like Netflix, GitHub, and Etsy use feature flags to deploy code hundreds of times per day while maintaining stability. As a result, teams gain more control over rollouts, testing, and experimentation. In this comprehensive guide, we will explore how feature flagging works, when to use it, and how tools like LaunchDarkly, Unleash, Flagsmith, and GrowthBook can support a smooth, controlled release process with practical code examples for multiple languages and frameworks.
What Is Feature Flagging
A feature flag (also called feature toggle or feature switch) is a conditional switch in your code that decides whether a feature should be active. Instead of shipping everything at once and hoping for the best, you wrap new features in flags and toggle them when ready.
// Simple feature flag concept
if (featureFlags.isEnabled('new-checkout')) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}
This enables gradual rollouts, A/B testing, and safe experimentation without code changes or redeployments. Furthermore, if something goes wrong, you can disable the feature instantly without rolling back your deployment.
Types of Feature Flags
• Release flags – Control feature visibility during rollout (short-lived)
• Experiment flags – A/B testing and multivariate experiments (medium-lived)
• Ops flags – Circuit breakers and kill switches (long-lived)
• Permission flags – User-tier features like premium vs free (long-lived)
Common Use Cases
• Gradual rollouts – Deploy to 1%, then 10%, then 50%, then 100%
• Beta testing – Enable features for specific user segments
• A/B testing – Compare different implementations with metrics
• Feature tiers – Premium vs free feature access
• Trunk-based development – Deploy incomplete work safely behind flags
• Kill switches – Instantly disable problematic features
• Canary releases – Test with internal users before public release
LaunchDarkly: Enterprise Feature Management
LaunchDarkly is the industry-leading platform for feature flag management. It provides real-time toggles, user targeting, experimentation, and analytics through a clean dashboard.
Key Features
• Real-time flag updates without redeployment (sub-200ms propagation)
• User-based targeting and complex segmentation rules
• Multivariate flags for A/B/n testing
• Built-in experimentation with statistical analysis
• Audit logs for compliance and debugging
• SDKs for 25+ languages and frameworks
• Edge computing support for low-latency evaluation
LaunchDarkly Node.js Example
// Install: npm install @launchdarkly/node-server-sdk
import * as LaunchDarkly from '@launchdarkly/node-server-sdk';
// Initialize the client
const ldClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY);
// Wait for initialization
await ldClient.waitForInitialization();
console.log('LaunchDarkly client initialized');
// Define user context
const context = {
kind: 'user',
key: 'user-123',
email: 'alice@example.com',
name: 'Alice',
custom: {
plan: 'premium',
country: 'US',
signupDate: '2024-01-15'
}
};
// Evaluate a boolean flag
const showNewDashboard = await ldClient.variation(
'new-dashboard',
context,
false // default value if flag not found
);
if (showNewDashboard) {
renderNewDashboard();
} else {
renderLegacyDashboard();
}
// Evaluate a multivariate flag (string)
const checkoutVersion = await ldClient.variation(
'checkout-experiment',
context,
'control'
);
switch (checkoutVersion) {
case 'control':
renderOriginalCheckout();
break;
case 'variant-a':
renderSimplifiedCheckout();
break;
case 'variant-b':
renderOnePageCheckout();
break;
default:
renderOriginalCheckout();
}
// Evaluate a JSON flag for configuration
const featureConfig = await ldClient.variation(
'pricing-tiers',
context,
{ basic: 9.99, pro: 29.99, enterprise: 99.99 }
);
console.log(`Pro tier price: $${featureConfig.pro}`);
// Track custom events for experiments
ldClient.track('checkout-completed', context, {
revenue: 49.99,
items: 3
});
// Clean shutdown
await ldClient.close();
LaunchDarkly React Example
// Install: npm install launchdarkly-react-client-sdk
import { withLDProvider, useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
// Wrap your app with the provider
const App = () => {
return (
);
};
export default withLDProvider({
clientSideID: process.env.REACT_APP_LD_CLIENT_ID,
context: {
kind: 'user',
key: 'anonymous',
},
options: {
bootstrap: 'localStorage', // Use cached flags initially
}
})(App);
// Use flags in components
function Dashboard() {
const { newDashboard, betaFeatures, experimentVariant } = useFlags();
const ldClient = useLDClient();
// Update user context when they log in
useEffect(() => {
if (user) {
ldClient.identify({
kind: 'user',
key: user.id,
email: user.email,
name: user.name,
custom: {
plan: user.subscription,
company: user.company
}
});
}
}, [user, ldClient]);
// Track events
const handlePurchase = (amount) => {
ldClient.track('purchase-completed', { revenue: amount });
};
if (newDashboard) {
return ;
}
return ;
}
// Conditional rendering with flags
function FeatureCard({ feature }) {
const { betaFeatures } = useFlags();
return (
{feature.name}
{feature.description}
{betaFeatures && feature.isBeta && (
Beta
)}
);
}
Unleash: Open-Source Feature Management
Unleash is one of the most popular open-source feature flag platforms, offering self-hosted or cloud deployment options.
Unleash Features
• Self-hosted or cloud deployment options
• Role-based access control for teams
• Advanced targeting rules with strategies
• SDKs for all major languages
• Metrics and analytics
• API-first design
Unleash Docker Setup
# docker-compose.yml
version: '3.8'
services:
unleash:
image: unleashorg/unleash-server:latest
ports:
- "4242:4242"
environment:
DATABASE_URL: postgres://unleash:password@db:5432/unleash
DATABASE_SSL: "false"
LOG_LEVEL: info
INIT_ADMIN_API_TOKENS: "*:*.unleash-admin-token"
depends_on:
- db
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:4242/health || exit 1
interval: 10s
timeout: 5s
retries: 5
db:
image: postgres:15
environment:
POSTGRES_DB: unleash
POSTGRES_USER: unleash
POSTGRES_PASSWORD: password
volumes:
- unleash_data:/var/lib/postgresql/data
volumes:
unleash_data:
Unleash Node.js Example
// Install: npm install unleash-client
import { initialize, isEnabled, getVariant } from 'unleash-client';
// Initialize the client
const unleash = initialize({
url: 'http://localhost:4242/api/',
appName: 'my-app',
instanceId: 'my-instance-1',
customHeaders: {
Authorization: process.env.UNLEASH_API_TOKEN,
},
});
// Wait for ready
unleash.on('ready', () => {
console.log('Unleash is ready');
});
unleash.on('error', (err) => {
console.error('Unleash error:', err);
});
// Check if feature is enabled
const context = {
userId: 'user-123',
sessionId: 'session-abc',
properties: {
plan: 'premium',
country: 'US'
}
};
if (isEnabled('new-checkout', context)) {
console.log('New checkout enabled');
} else {
console.log('Using legacy checkout');
}
// Get variant for A/B testing
const variant = getVariant('checkout-experiment', context);
console.log(`Variant: ${variant.name}`);
console.log(`Payload: ${JSON.stringify(variant.payload)}`);
switch (variant.name) {
case 'A':
renderVariantA();
break;
case 'B':
renderVariantB();
break;
default:
renderControl();
}
Flagsmith: API-First Feature Flags
Flagsmith offers both cloud-hosted and self-hosted options with an API-first approach and excellent frontend SDK support.
Flagsmith Features
• User segments and remote config
• API-first approach for flexibility
• Strong frontend and mobile SDK support
• Audit logs and approval workflows
• Environment-based configuration
Flagsmith JavaScript Example
// Install: npm install flagsmith
import flagsmith from 'flagsmith';
// Initialize with environment ID
await flagsmith.init({
environmentID: process.env.FLAGSMITH_ENV_ID,
onChange: (oldFlags, params) => {
console.log('Flags updated:', params);
},
});
// Identify user for targeting
await flagsmith.identify('user-123', {
email: 'alice@example.com',
plan: 'premium',
company_size: 50
});
// Check boolean flag
if (flagsmith.hasFeature('new-dashboard')) {
renderNewDashboard();
}
// Get feature value (remote config)
const maxUploadSize = flagsmith.getValue('max-upload-size', 10); // default 10MB
console.log(`Max upload: ${maxUploadSize}MB`);
// Get all flags
const allFlags = flagsmith.getAllFlags();
console.log('All flags:', allFlags);
// React hook integration
import { FlagsmithProvider, useFlags, useFlagsmith } from 'flagsmith/react';
function App() {
return (
);
}
function Dashboard() {
const flags = useFlags(['new-dashboard', 'beta-features']);
const flagsmith = useFlagsmith();
useEffect(() => {
if (user) {
flagsmith.identify(user.id, { plan: user.plan });
}
}, [user]);
return (
{flags.new_dashboard?.enabled && }
);
}
GrowthBook: Experimentation-Focused
GrowthBook is an open-source A/B testing and feature flagging tool that excels at experiment analysis with statistical rigor.
GrowthBook Features
• Easy integration with minimal setup
• Detailed experiment analytics with Bayesian statistics
• SDK for React, Node, Python, and more
• Visual editor for non-developers
• Data warehouse integration
GrowthBook React Example
// Install: npm install @growthbook/growthbook-react
import { GrowthBook, GrowthBookProvider, useFeatureIsOn, useFeatureValue } from '@growthbook/growthbook-react';
// Create GrowthBook instance
const gb = new GrowthBook({
apiHost: 'https://cdn.growthbook.io',
clientKey: process.env.REACT_APP_GROWTHBOOK_KEY,
enableDevMode: process.env.NODE_ENV === 'development',
trackingCallback: (experiment, result) => {
// Send to analytics
analytics.track('Experiment Viewed', {
experimentId: experiment.key,
variationId: result.key,
});
},
});
// Load features from API
await gb.loadFeatures();
// Set user attributes for targeting
gb.setAttributes({
id: user.id,
email: user.email,
country: user.country,
plan: user.plan,
deviceType: isMobile ? 'mobile' : 'desktop',
});
// Provider component
function App() {
return (
);
}
// Use in components
function CheckoutPage() {
// Boolean flag
const showNewCheckout = useFeatureIsOn('new-checkout');
// Multivariate experiment
const buttonColor = useFeatureValue('checkout-button-color', 'blue');
// JSON configuration
const checkoutConfig = useFeatureValue('checkout-config', {
showPromoCode: true,
maxItems: 10
});
return (
{showNewCheckout ? (
) : (
)}
);
}
OpenFeature: Vendor-Agnostic Standard
OpenFeature is a CNCF sandbox project that provides a standardized API for feature flags, allowing you to switch between providers without rewriting code.
// Install: npm install @openfeature/server-sdk
// Plus your provider: npm install @openfeature/launchdarkly-provider
import { OpenFeature } from '@openfeature/server-sdk';
import { LaunchDarklyProvider } from '@openfeature/launchdarkly-provider';
// Set up provider (can be switched without code changes)
OpenFeature.setProvider(
new LaunchDarklyProvider(process.env.LAUNCHDARKLY_SDK_KEY)
);
// Get client
const client = OpenFeature.getClient();
// Evaluate flags with standard API
const context = {
targetingKey: 'user-123',
email: 'alice@example.com',
plan: 'premium'
};
// Boolean evaluation
const showNewDashboard = await client.getBooleanValue(
'new-dashboard',
false, // default
context
);
// String evaluation
const theme = await client.getStringValue(
'ui-theme',
'light',
context
);
// Number evaluation
const maxItems = await client.getNumberValue(
'cart-max-items',
10,
context
);
// Object evaluation
const config = await client.getObjectValue(
'feature-config',
{ enabled: false },
context
);
// Get detailed evaluation with reason
const details = await client.getBooleanDetails(
'new-dashboard',
false,
context
);
console.log(`Value: ${details.value}`);
console.log(`Reason: ${details.reason}`);
console.log(`Variant: ${details.variant}`);
Feature Flag Patterns
Percentage Rollout
// Gradual rollout pattern
const rolloutConfig = {
'new-checkout': {
enabled: true,
percentage: 25, // 25% of users
rules: [
{ segment: 'internal', percentage: 100 }, // All internal users
{ segment: 'beta', percentage: 100 }, // All beta users
{ segment: 'default', percentage: 25 } // 25% of others
]
}
};
// Typical rollout schedule:
// Day 1: 1% (catch critical bugs)
// Day 2: 10% (monitor metrics)
// Day 3: 25% (broader testing)
// Day 5: 50% (half of users)
// Day 7: 100% (full rollout)
User Segmentation
// Target specific user segments
const segmentRules = {
'premium-dashboard': {
rules: [
{
conditions: [
{ attribute: 'plan', operator: 'in', values: ['pro', 'enterprise'] }
],
variation: true
},
{
conditions: [],
variation: false // Default for others
}
]
},
'us-only-feature': {
rules: [
{
conditions: [
{ attribute: 'country', operator: 'equals', value: 'US' }
],
variation: true
}
]
}
};
Kill Switch Pattern
// Wrap risky operations with kill switches
async function processPayment(order) {
// Kill switch for payment processing
if (!featureFlags.isEnabled('payments-enabled')) {
throw new ServiceUnavailableError(
'Payment processing is temporarily disabled'
);
}
// Circuit breaker for third-party service
if (!featureFlags.isEnabled('stripe-integration')) {
return fallbackPaymentProcessor(order);
}
return stripePaymentProcessor(order);
}
// Rate limiting flag
async function handleApiRequest(req) {
const rateLimit = featureFlags.getValue('api-rate-limit', 100);
if (await isRateLimited(req.userId, rateLimit)) {
throw new TooManyRequestsError();
}
return processRequest(req);
}
Best Practices for Feature Flagging
Naming Conventions
// Use consistent naming patterns
const flagNamingExamples = {
// Format: [scope].[feature].[variant]
// Release flags (temporary)
'release.new-checkout': true,
'release.redesigned-dashboard': true,
// Experiment flags (temporary)
'experiment.checkout-button-color': 'blue',
'experiment.pricing-page-layout': 'variant-b',
// Ops flags (permanent)
'ops.enable-caching': true,
'ops.rate-limit-requests': 1000,
// Permission flags (permanent)
'permission.premium-features': false,
'permission.api-access': true,
// Bad examples (avoid)
'newFeature': true, // Not descriptive
'test123': true, // Meaningless
'johns-experiment': true, // Personal names
};
Flag Lifecycle Management
// Track flag metadata
const flagMetadata = {
'release.new-checkout': {
owner: 'checkout-team',
createdAt: '2025-01-01',
expiresAt: '2025-02-01', // Schedule removal
ticket: 'JIRA-1234',
description: 'New streamlined checkout flow',
type: 'release',
environments: ['development', 'staging', 'production']
}
};
// Automated cleanup script
async function findStaleFlags() {
const flags = await flagService.getAllFlags();
const now = new Date();
const staleFlags = flags.filter(flag => {
if (!flag.expiresAt) return false;
return new Date(flag.expiresAt) < now;
});
return staleFlags;
}
// Report stale flags in CI/CD
async function checkForStaleFlags() {
const staleFlags = await findStaleFlags();
if (staleFlags.length > 0) {
console.warn('Found stale flags that should be removed:');
staleFlags.forEach(flag => {
console.warn(` - ${flag.name} (expired: ${flag.expiresAt})`);
});
}
}
Testing with Feature Flags
// Unit tests should test both states
describe('Checkout', () => {
it('renders new checkout when flag is enabled', () => {
mockFlags({ 'new-checkout': true });
render( );
expect(screen.getByTestId('new-checkout')).toBeInTheDocument();
});
it('renders legacy checkout when flag is disabled', () => {
mockFlags({ 'new-checkout': false });
render( );
expect(screen.getByTestId('legacy-checkout')).toBeInTheDocument();
});
});
// Integration tests with flag overrides
describe('Checkout E2E', () => {
it('completes purchase with new checkout', () => {
cy.setFeatureFlags({ 'new-checkout': true });
cy.visit('/checkout');
cy.get('[data-testid="new-checkout"]').should('exist');
cy.get('[data-testid="place-order"]').click();
cy.url().should('include', '/confirmation');
});
});
Common Mistakes to Avoid
Avoid these common pitfalls that lead to technical debt and operational issues.
1. Never Cleaning Up Flags
// ❌ Bad: Flags accumulate forever
// 200+ flags, most abandoned
// ✅ Good: Set expiration dates and review regularly
// - Remove release flags within 2 weeks of 100% rollout
// - Archive experiment flags after analysis complete
// - Review all flags monthly
2. No Default Values
// ❌ Bad: No fallback if service is down
const enabled = await ldClient.variation('new-feature', user);
// Returns undefined if service unavailable!
// ✅ Good: Always provide sensible defaults
const enabled = await ldClient.variation('new-feature', user, false);
// Returns false if service unavailable
3. Nesting Flags Too Deeply
// ❌ Bad: Complex nested conditions
if (flags.newDashboard) {
if (flags.dashboardV2) {
if (flags.dashboardV2Charts) {
renderV2Charts();
}
}
}
// ✅ Good: Flat, independent flags
const chartVersion = flags.getValue('dashboard-charts-version', 'v1');
renderCharts(chartVersion);
4. Using Flags for Permanent Configuration
// ❌ Bad: Feature flag for permanent setting
const apiUrl = flags.getValue('api-url', 'https://api.example.com');
// ✅ Good: Use environment variables for configuration
const apiUrl = process.env.API_URL;
// Feature flags are for temporary toggles, not permanent config
Conclusion
Feature flagging gives teams a safer, smarter way to deploy software. With tools like LaunchDarkly for enterprise needs, Unleash and Flagsmith for self-hosted flexibility, GrowthBook for experimentation focus, or OpenFeature for vendor-agnostic implementations, you can ship features gradually, test safely with real users, and react instantly when issues arise.
The key to successful feature flagging is treating flags as temporary tools with clear ownership, expiration dates, and cleanup processes. Whether you choose a fully managed platform or an open-source alternative, feature flags help you reduce deployment risk and increase release velocity.
For related topics, explore Blue/Green vs Canary Deployments: When and How to Use Each to learn how flags support progressive delivery. For CI/CD pipeline integration, see CI/CD for Node.js Projects Using GitHub Actions. You can also visit the LaunchDarkly developer guide, the Unleash documentation, the Flagsmith documentation, and the OpenFeature specification for deeper exploration of feature flag implementations.