On a spaceship, before activating a new navigation system for the entire fleet, you test it on one ship. If it works -- you deploy it to the rest. If not -- you turn it off with a single switch. This is exactly what Feature Flags are -- a mechanism that allows enabling and disabling features without deploying new code.
A Feature Flag (feature toggle) is a boolean value or configuration that determines whether a given feature is available:
1// Simplest form -- object with flags
2const featureFlags = {
3 newDashboard: true,
4 darkMode: true,
5 experimentalNav: false,
6 betaSearch: false,
7};
8
9// Usage in a component
10function App() {
11 return (
12 <div>
13 {featureFlags.newDashboard
14 ? <NewDashboard />
15 : <OldDashboard />
16 }
17 {featureFlags.betaSearch && <BetaSearchBar />}
18 </div>
19 );
20}In a real application, we store flags in context so they're available everywhere:
1const FeatureFlagContext = createContext({});
2
3function FeatureFlagProvider({ flags, children }) {
4 return (
5 <FeatureFlagContext.Provider value={flags}>
6 {children}
7 </FeatureFlagContext.Provider>
8 );
9}
10
11function useFeatureFlag(flagName) {
12 const flags = useContext(FeatureFlagContext);
13 return flags[flagName] ?? false;
14}
15
16// Usage
17function NavigationBar() {
18 const showNewNav = useFeatureFlag('newNavigation');
19
20 return showNewNav ? <NewNavBar /> : <ClassicNavBar />;
21}Instead of writing if/else in every component, we can create a declarative component:
1function Feature({ name, children, fallback = null }) {
2 const isEnabled = useFeatureFlag(name);
3 return isEnabled ? children : fallback;
4}
5
6// Usage -- readable and declarative
7function MissionControl() {
8 return (
9 <div>
10 <h1>Mission Control</h1>
11
12 <Feature name="advancedRadar">
13 <AdvancedRadarPanel />
14 </Feature>
15
16 <Feature
17 name="newStatusBar"
18 fallback={<LegacyStatusBar />}
19 >
20 <ModernStatusBar />
21 </Feature>
22
23 <Feature name="experimentalWarpDrive">
24 <WarpDriveControls />
25 </Feature>
26 </div>
27 );
28}A/B Testing is an extension of feature flags where different users see different versions of the interface:
1function useABTest(testName, variants) {
2 const [variant, setVariant] = useState(null);
3
4 useEffect(() => {
5 // Check if user already has an assigned variant
6 const stored = localStorage.getItem(`ab_${testName}`);
7 if (stored && variants.includes(stored)) {
8 setVariant(stored);
9 } else {
10 // Randomly assign a variant
11 const randomVariant = variants[
12 Math.floor(Math.random() * variants.length)
13 ];
14 localStorage.setItem(`ab_${testName}`, randomVariant);
15 setVariant(randomVariant);
16 }
17 }, [testName, variants]);
18
19 return variant;
20}
21
22// Usage
23function LandingPage() {
24 const variant = useABTest('landing_cta', ['control', 'variant_a', 'variant_b']);
25
26 if (variant === 'control') {
27 return <ClassicHero ctaText="Start your mission" />;
28 }
29 if (variant === 'variant_a') {
30 return <ClassicHero ctaText="Join the fleet!" />;
31 }
32 return <ModernHero ctaText="Launch now" />;
33}Instead of enabling a flag for everyone at once, you can deploy gradually -- e.g., 10% of users, then 50%, then 100%:
1function usePercentageRollout(featureName, percentage) {
2 const [isEnabled, setIsEnabled] = useState(false);
3
4 useEffect(() => {
5 // Deterministic hash based on userId + featureName
6 const stored = localStorage.getItem(`rollout_${featureName}`);
7 if (stored !== null) {
8 setIsEnabled(stored === 'true');
9 } else {
10 const random = Math.random() * 100;
11 const enabled = random < percentage;
12 localStorage.setItem(`rollout_${featureName}`, String(enabled));
13 setIsEnabled(enabled);
14 }
15 }, [featureName, percentage]);
16
17 return isEnabled;
18}
19
20// Usage
21function Dashboard() {
22 const showNewChart = usePercentageRollout('new_chart_v2', 30); // 30% of users
23
24 return (
25 <div>
26 {showNewChart ? <NewInteractiveChart /> : <LegacyChart />}
27 </div>
28 );
29}In production, flags often come from the server (LaunchDarkly, Flagsmith, Unleash):
1function useRemoteFlags() {
2 const [flags, setFlags] = useState({});
3 const [loading, setLoading] = useState(true);
4
5 useEffect(() => {
6 fetch('/api/feature-flags')
7 .then(res => res.json())
8 .then(data => {
9 setFlags(data);
10 setLoading(false);
11 })
12 .catch(() => setLoading(false));
13 }, []);
14
15 return { flags, loading };
16}
17
18// Usage with FeatureFlagProvider
19function App() {
20 const { flags, loading } = useRemoteFlags();
21
22 if (loading) return <Spinner />;
23
24 return (
25 <FeatureFlagProvider flags={flags}>
26 <Dashboard />
27 </FeatureFlagProvider>
28 );
29}| Situation | Flag type | |-----------|-----------| | New feature under development | Boolean flag (
true/false) |
| Testing different UI versions | A/B test (variants) |
| Gradual deployment to users | Percentage rollout |
| Quick disable of problematic feature | Kill switch |
| Different features for different plans | User-based flag |Feature Flags are like a central control panel on a spaceship. You can enable or disable any system with a single button, test a new engine on one ship before deploying it across the entire fleet, and immediately roll back changes when something goes wrong -- without having to land at a station and replace parts!