React Feature Flags Explained: A Beginner's Guide
A feature flag is a conditional that controls whether a code path executes in production without requiring a redeploy. Instead of shipping new code and hoping it works, you ship the code off, and toggle it on for a subset of users via a configuration service—or instantly kill it if bugs appear. Feature flags are the safety net that lets teams deploy daily without fear.
I've been building production React apps for 8 years and used feature flags in every major product launch at scale. Early on, I learned the hard way: a missing edge case in a new checkout flow hit 100% of users simultaneously, causing a revenue spike to crash mid-lunch. A feature flag would have let us catch it on 1% first, measure impact, and roll back in seconds. Now, feature flags are non-negotiable infrastructure.
What is a Feature Flag and Why Does It Matter?
A feature flag is a runtime toggle—usually a boolean or string value stored in a configuration service—that determines whether your React app executes a code block. Rather than branching at deployment time (feature "shipped" or "not shipped"), you branch at execution time. This decouples deployment from release.
"The key insight is that feature flags transform deployment from a binary event—in or out—into a continuous, reversible process. You can deploy a feature to 1% of users on Monday, measure for a week, and expand to 50% on Friday, all without touching your code or CI/CD pipeline." — Jez Humble, Continuous Delivery author.
Deployment vs. Release: Deployment pushes code to servers. Release makes a feature available to users. Feature flags let them happen independently—you deploy code with a flag off, then release it later by toggling the flag on.
Key benefits:
- Kill switches: Turn off a broken feature in seconds without redeployment.
- Gradual rollout: Test with 1%, 10%, 50% of users before going 100%.
- A/B experiments: Show feature A to group 1, feature B to group 2, measure which converts better.
- Environment consistency: Use the same code in dev, staging, and production; flags control behavior per environment.
- Team autonomy: Multiple teams can ship independently even if their code lands in the same release branch.
Types of Feature Flags
Feature flags come in several flavors, each suited to different timescales and complexity.
Release Toggles (Short-lived, days to weeks)
Release toggles switch off incomplete features during development so the team can merge work-in-progress code to the main branch without breaking production. Once the feature is live and stable, the flag is deleted.
Lifespan: days to 4 weeks. Example: A new dashboard design is merged to main but flagged off for all users. QA toggles it on in staging. Once verified, the flag is gradually expanded: 10% of prod users, then 50%, then removed entirely. Total time: 1–2 weeks.
Ops Toggles (Hours to days)
Ops toggles are kill switches: if a feature causes a spike in errors, latency, or database load, ops can turn it off instantly without a deploy. Unlike release toggles, ops toggles stay in code for months and are rarely touched.
Lifespan: weeks to months; reused per incident.
Example: A payment processing feature flags behind isPaymentV2Enabled. If the provider's API goes down, your on-call engineer toggles it off and users fall back to the old processor. Your team rolls out a fix, toggles it back on.
Experiment Toggles (Weeks)
Experiment toggles run A/B tests: 50% of users see variant A, 50% see variant B. You measure conversion, bounce rate, session duration, and calculate statistical significance. Winners are promoted to release toggles; losers are deleted.
Lifespan: 2–4 weeks per experiment.
Example: You're testing a new checkout flow. Half your users see the old flow, half the new. After 2 weeks, you analyze: new flow has 12% higher conversion (p < 0.05). You promote it to a release toggle, gradually expand to 100%, and delete the experiment.
Permission Toggles (Months)
Permission toggles gate features by user role, account tier, or organization. A feature might be "beta" (only for paying enterprise customers) or "beta" (invite-only). Permission toggles can live indefinitely.
Example: A team collaboration feature is available only to "Pro" and "Enterprise" tiers, not "Starter". The flag checks user.tier >= 'Pro' on every render.
How Flags Integrate into React
In React, a feature flag is usually a runtime value fetched from a configuration service and stored in context or state. When the app loads, you fetch flags for the current user (based on ID, session, or traits like geography), then conditionally render components or branches.
// Pseudocode: simple flag check in a React component
import { useFeatureFlag } from './useFeatureFlag';
export function Checkout() {
const isPaymentV2Enabled = useFeatureFlag('payment_v2');
return (
<div>
{isPaymentV2Enabled ? (
<PaymentV2 />
) : (
<PaymentLegacy />
)}
</div>
);
}
The useFeatureFlag hook queries a cache of flags fetched at app startup. The cache is populated from a config service (like LaunchDarkly or a self-hosted API) and updated periodically or on demand.
// Custom hook: fetch and cache flags
import { useContext, useEffect, useState } from 'react';
const FlagContext = React.createContext({});
export function useFeatureFlag(flagName) {
const flags = useContext(FlagContext);
return flags[flagName] ?? false; // default false if not loaded
}
export function FlagProvider({ userId, children }) {
const [flags, setFlags] = useState({});
useEffect(() => {
// Fetch flags for this user from your config service
fetch(`/api/flags?userId=${userId}`)
.then((r) => r.json())
.then(setFlags)
.catch((e) => console.error('Flag fetch error:', e));
}, [userId]);
return (
<FlagContext.Provider value={flags}>{children}</FlagContext.Provider>
);
}
Flag Configurations and Rules
Behind the scenes, your config service stores flag rules. A rule defines which users see which variant. Rules can be:
- Static: always on, always off.
- Percentage-based: 50% of users see the flag, 50% don't (hash-based so the same user always sees the same variant).
- Rule-based: if
user.tier === 'Enterprise', flag is on; ifuser.location === 'US', flag is on. - Custom: arbitrary logic (user ID in a whitelist, time of day, A/B experiment assignment).
A typical rule configuration (JSON):
{
"flagName": "new_dashboard",
"enabled": true,
"rules": [
{
"name": "Beta users only",
"condition": { "betaUser": true },
"percentage": 100
},
{
"name": "General rollout",
"condition": {},
"percentage": 25
}
]
}
Beta users (100%) and 25% of everyone else see the new dashboard. The remaining 75% see the old one.
Key Takeaways
- A feature flag is a runtime conditional that decouples deployment from release, enabling safe rollouts and instant rollbacks.
- Release toggles (short-lived) control in-progress features; ops toggles (persistent) are kill switches; experiment toggles measure A/B variants; permission toggles gate by user tier.
- Feature flags are usually fetched from a configuration service at app startup and cached in React context or state.
- Rules in the config service determine which users see which variant (percentage-based, rule-based, custom).
- Feature flags are essential for teams shipping daily and reduce blast radius when bugs slip through.
Frequently Asked Questions
What's the difference between a feature flag and a feature branch?
A feature branch is a Git branch where you develop a feature in isolation. A feature flag is a runtime conditional. They're complementary: you develop on a feature branch, merge to main with the flag off, and release by toggling the flag. Feature branches reduce merge conflicts; feature flags enable continuous deployment.
Can I use feature flags for user permissions (e.g., admin vs. regular user)?
Yes, but permission toggles are subtly different from experiment or release toggles. A permission toggle's rule is usually a hard attribute (user role, account tier), not a percentage rollout. The same user always sees the same variant (deterministic). Experiment toggles, by contrast, must hash users consistently so the same user always sees the same variant, but the assignment is probabilistic across the user base.
How do I avoid flag bloat?
Delete flags when they're no longer needed. A release toggle should be removed 2–4 weeks after full rollout. Ops toggles can linger (they're rarely touched). Set a quarterly review: audit all active flags, archive old ones, and consolidate related flags into fewer, clearer toggles.
Do I need a third-party service like LaunchDarkly, or can I build my own?
Both are viable. Building your own is simpler for startups (just a config file or API endpoint), but third-party services add analytics, targeting rules, audit logs, and team management out of the box. As you scale, the operational overhead of self-hosting can exceed the licensing cost. Start simple, migrate to a service when you need multi-variant targeting or experimentation analytics.
How do feature flags affect bundle size?
Minimally. Flags are usually stored as a flat JSON object (100 KB for 1,000 flags is typical) and fetched at startup. Dead code elimination won't remove flagged-off branches at build time (the flag value is dynamic), so your bundle includes all variants. To optimize, use dynamic imports: lazy-load expensive features only when their flag is on.