Feature Flags
LaunchDarkly
import * as LaunchDarkly from '@launchdarkly/node-server-sdk';
const client = LaunchDarkly.init(process.env.LD_SDK_KEY!); await client.waitForInitialization();
// Evaluate flag const showNewUI = await client.variation('new-dashboard-ui', { key: user.id, email: user.email, custom: { plan: user.plan }, }, false); // default value
if (showNewUI) { renderNewDashboard(); } else { renderLegacyDashboard(); }
React SDK
import { useFlags, withLDProvider } from 'launchdarkly-react-client-sdk';
function Dashboard() { const { newDashboardUi, maxUploadSize } = useFlags();
return newDashboardUi ? <NewDashboard maxUpload={maxUploadSize} /> : <LegacyDashboard />; }
// Wrap app export default withLDProvider({ clientSideID: process.env.NEXT_PUBLIC_LD_CLIENT_ID!, context: { kind: 'user', key: user.id, email: user.email }, })(App);
Unleash (self-hosted)
import { initialize } from 'unleash-client';
const unleash = initialize({ url: 'https://unleash.example.com/api', appName: 'my-app', customHeaders: { Authorization: process.env.UNLEASH_API_KEY! }, });
if (unleash.isEnabled('new-checkout', { userId: user.id })) { // New checkout flow }
Custom Implementation (simple)
interface FeatureFlags { [key: string]: { enabled: boolean; rolloutPercentage?: number; allowedUsers?: string[]; }; }
class FlagService { constructor(private flags: FeatureFlags) {}
isEnabled(flag: string, userId?: string): boolean { const config = this.flags[flag]; if (!config?.enabled) return false; if (config.allowedUsers?.includes(userId!)) return true; if (config.rolloutPercentage != null && userId) { const hash = simpleHash(userId + flag) % 100; return hash < config.rolloutPercentage; } return config.enabled; } }
Flag Types
Type Purpose Example
Boolean On/off toggle new-ui-enabled
Percentage rollout Gradual release 10% → 50% → 100%
User targeting Specific users Beta testers, internal
Multivariate A/B/C testing checkout-variant: "A"
Kill switch Emergency disable payments-enabled
Anti-Patterns
Anti-Pattern Fix
Flags that live forever Set expiration, clean up after full rollout
Flag checks deep in code Check at entry points, pass result down
No default values Always provide sensible defaults
Testing only flagged-on path Test both paths in CI
Flags in tight loops Cache flag evaluation result
Production Checklist
-
Default values for all flags (graceful degradation)
-
Flag naming convention (feature-name-action )
-
Cleanup process for fully-rolled-out flags
-
Monitoring: flag evaluation metrics
-
Testing both flag states in CI
-
Audit log of flag changes