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.

Back to Journal