Handbook
/
Product & Engineering
Feature Flags and Progressive Rollouts
Feature flags let you ship code without shipping features. Here's how to use them for safer, faster releases.
Feature flags (also called feature toggles) are one of the most powerful tools for modern software delivery. They let you deploy code to production without exposing it to users, enabling progressive rollouts, A/B testing, and instant rollbacks.
What Feature Flags Are
A feature flag is a conditional in your code that controls whether a feature is active:
if (featureEnabled('new-checkout-flow')) { renderNewCheckout(); } else { renderOldCheckout(); }
The flag can be flipped without deploying code. This separates deployment (getting code to production) from release (exposing features to users).
Why They Matter
Deploy and Release Separately
Without feature flags, deploying code means releasing it. If something breaks, you need to deploy again to fix it.
With feature flags, you deploy frequently (even continuously), but control when features become visible. Broken feature? Flip the flag off. No deployment needed.
Progressive Rollouts
Instead of all-or-nothing releases:
1.
Roll out to 5% of users
2.
Monitor for issues
3.
If stable, roll out to 25%, then 50%, then 100%
This catches problems early, when they affect few users.
A/B Testing
Compare feature variants:
Show variant A to 50% of users
Show variant B to 50% of users
Measure which performs better
Feature flags make this trivial.
Kill Switches
Critical feature causing problems? Flip the flag off instantly. No deployment, no waiting for CI/CD, no stressful hotfixes.
Gradual Feature Development
Merge code that isn’t complete behind a flag. Multiple engineers can work on the same feature without blocking each other. When it’s ready, flip the flag.
Types of Feature Flags
Release Flags
Control whether a feature is visible to users. Usually temporary—removed once the feature is fully launched.
Experiment Flags
A/B test variants. Tied to specific experiments with start and end dates.
Ops Flags
Control operational aspects: load shedding, circuit breakers, maintenance mode. Usually long-lived.
Permission Flags
Control access to features based on user attributes: subscription tier, geographic location, account type.
Implementation Patterns
Basic Boolean Flag
const newPricingEnabled = await featureFlags.isEnabled('new-pricing', { userId }); if (newPricingEnabled) { showNewPricing(); } else { showOldPricing(); }
Percentage Rollout
// Configure: 'new-checkout' enabled for 20% of users const enabled = await featureFlags.isEnabled('new-checkout', { userId });
The flag service handles randomization consistently per user.
User Targeting
// Enable for specific users, teams, or segments const enabled = await featureFlags.isEnabled('beta-feature', { userId, email: user.email, plan: user.plan, });
Multivariate Flags
// Return specific variants instead of boolean const variant = await featureFlags.getVariant('checkout-layout', { userId }); switch (variant) { case 'single-page': renderSinglePageCheckout(); break; case 'multi-step': renderMultiStepCheckout(); break; default: renderOriginalCheckout(); }
Tools and Services
SaaS Options
LaunchDarkly: Industry leader. Full-featured, enterprise-grade.
PostHog: Open source with feature flags + analytics.
Statsig: Strong experimentation focus.
Flagsmith: Open source, self-hostable.
Split: Enterprise-focused.
ConfigCat: Simple and affordable.
Build vs Buy
For most startups, buy. Feature flag infrastructure is:
Not your core competency
Surprisingly complex to build well
Critical infrastructure that needs high reliability
Building your own makes sense only if you have unusual requirements or strong opinions about implementation.
DIY Option
If you must build it yourself, start simple:
// Simple feature flag service const flags = { 'new-checkout': { enabled: true, percentage: 20, userIds: ['user-123', 'user-456'], // Force-enable for these users }, }; function isEnabled(flagName: string, context: { userId: string }): boolean { const flag = flags[flagName]; if (!flag?.enabled) return false; // Force-enabled users if (flag.userIds?.includes(context.userId)) return true; // Percentage rollout const hash = hashString(flagName + context.userId); return (hash % 100) < flag.percentage; }
Store flag configuration in a database or config file. Update without deploys.
Best Practices
Naming Conventions
Use clear, consistent naming:
enable-new-checkout (release flag)
experiment-pricing-page-v2 (experiment)
ops-maintenance-mode (ops flag)
permission-enterprise-analytics (permission flag)
Include the type in the name.
Default to Off
New flags should default to off. This ensures incomplete features don’t accidentally get exposed.
Minimize Flag Scope
Wrap the minimum amount of code. Don’t put entire pages behind flags when you only need to hide a button.
Clean Up Old Flags
Flags left in code become tech debt. When a flag is fully rolled out:
1.
Remove the flag check
2.
Delete the old code path
3.
Remove the flag from your flag service
Set calendar reminders to clean up after rollouts.
Test Both Paths
Your test suite should test both flag states. A bug in the “flag off” path is still a bug.
Document Flags
Maintain a registry of active flags:
Flag
Purpose
Owner
Created
Status
new-checkout
Simplified checkout flow
@sarah
2024-01-15
Rolling out (50%)
pricing-experiment
Test new pricing page
@mike
2024-02-01
Running
Avoid Flag Dependencies
Don’t nest flags or create complex dependencies between them. If feature B only works when feature A is enabled, either:
Roll them out together
Ensure B gracefully handles A being off
Progressive Rollout Strategy
Phase 1: Internal (0-1%)
Enable for your team. Catch obvious bugs.
Phase 2: Beta Users (1-5%)
Enable for users who’ve opted into early access. Get feedback.
Phase 3: Early Rollout (5-25%)
Start rolling out to general users. Monitor closely.
Phase 4: Wider Rollout (25-75%)
If stable, continue expanding. Keep monitoring.
Phase 5: Full Rollout (100%)
Feature is live for everyone. Monitor for a few days, then clean up the flag.
Rollback Criteria
Define when you’ll roll back:
Error rate increases by X%
Latency increases by Y ms
Conversion rate drops by Z%
Don’t wait for things to get catastrophic. Roll back early, investigate, fix, re-roll out.
Monitoring and Observability
Feature flags need observability:
Track Flag Evaluations
Log which flags are evaluated and their results. This helps debug issues.
Segment Metrics by Flag
Your analytics should filter by feature flag state. Compare metrics for users with the flag on vs. off.
Set Alerts
Alert when flag changes cause metric changes. Automated detection catches problems you might miss.
Common Mistakes
Too many flags: Flag proliferation makes code hard to reason about. Clean up aggressively.
Flags that never get removed: Every flag should have an expiration date.
Testing only the happy path: Test both flag states in your CI.
No monitoring: Rolling out blind is dangerous. Always monitor.
Complex flag dependencies: Keep flags independent.
Using flags for configuration: Feature flags are for feature control, not app configuration. Use config management for settings.
Key Takeaways
Feature flags separate deployment from release
Use them for progressive rollouts, A/B tests, kill switches
Start with a SaaS solution (LaunchDarkly, PostHog, etc.)
Clean up old flags aggressively—they’re tech debt
Test both flag states, monitor rollouts, define rollback criteria
Roll out progressively: internal → beta → 5% → 25% → 100%
AIMake has access to all of this
Our AI has access to the entire Startup Handbook. Ask it anything about building your startup.
Get started
Previous
The YC Advice: Do Things That Don't Scale
Next
How to Build an MVP That Actually Validates