Feature Flags: Ship Faster, Safer
Separate deployment from release. Feature flags let you ship code without exposing features.
The Problem
Traditional workflow: 1. Build feature 2. Merge to main 3. Deploy to production 4. Feature is live
What if the feature is broken? Rollback. Redeploy. Stressful.
Feature Flags
Wrap features in conditionals:
if (featureFlags.newCheckout) {
return <NewCheckout />;
}
return <OldCheckout />;
Now you can:
- Deploy code without releasing features
- Enable features for specific users
- Gradually roll out to percentages
- Instantly disable broken features
Simple Implementation
// flags.js
const flags = {
newCheckout: process.env.FEATURE_NEW_CHECKOUT === 'true',
darkMode: process.env.FEATURE_DARK_MODE === 'true',
betaFeatures: process.env.FEATURE_BETA === 'true',
};
export default flags;
// Usage
import flags from './flags';
function App() {
return (
<div>
{flags.darkMode && <DarkModeToggle />}
{flags.newCheckout ? <NewCheckout /> : <Checkout />}
</div>
);
}
Dynamic Flags
For runtime control, fetch flags from an API:
// FlagProvider.jsx
const FlagContext = createContext({});
function FlagProvider({ children }) {
const [flags, setFlags] = useState({});
useEffect(() => {
fetch('/api/feature-flags')
.then(r => r.json())
.then(setFlags);
}, []);
return (
<FlagContext.Provider value={flags}>
{children}
</FlagContext.Provider>
);
}
// useFlag hook
function useFlag(flagName) {
const flags = useContext(FlagContext);
return flags[flagName] ?? false;
}
// Usage
function Checkout() {
const showNewCheckout = useFlag('newCheckout');
return showNewCheckout ? <NewCheckout /> : <OldCheckout />;
}
Percentage Rollouts
function isEnabled(flagName, userId) {
const flag = flags[flagName];
if (typeof flag === 'boolean') return flag;
if (flag.percentage) {
// Deterministic based on user ID
const hash = hashCode(userId + flagName);
return (hash % 100) < flag.percentage;
}
return false;
}
// 10% of users get the feature
flags.newCheckout = { percentage: 10 };
Flag Services
For production, consider:
- **LaunchDarkly** - Full-featured, enterprise
- **Split** - A/B testing focused
- **Unleash** - Open source
- **ConfigCat** - Simple and affordable
Or roll your own for simple needs.
Best Practices
1. **Name clearly** - `showNewCheckout` not `flag123` 2. **Default to off** - New flags should be disabled 3. **Remove old flags** - Technical debt if they linger 4. **Document flags** - What does each flag control? 5. **Test both states** - Don't forget the old code path
Example Workflow
1. Build feature behind flag (off by default) 2. Deploy to production 3. Enable for internal users (QA) 4. Enable for 5% of users 5. Monitor metrics 6. Gradually increase to 100% 7. Remove flag, delete old code
Ship with confidence.