A feature flag (feature toggle) is a condition in your code that turns a feature on or off at runtime, without deploying again. Flags let you merge and deploy unfinished work safely, release features to a few users first, and switch something off instantly if it misbehaves. They’re one of the simplest tools for safer releases.
Why use them
- Deploy ≠ release: code reaches production “dark”, and you enable it when ready.
- Gradual rollouts: 1% → 10% → 100% of users, watching metrics at each step.
- Kill switches: turn off a broken or expensive feature in seconds.
- Trunk-based development: no long-lived feature branches. See Git branching strategies.
- Beta programmes and A/B tests: enable for specific users or groups.
Types of flags
| Type | Lifetime | Example |
|---|---|---|
| Release flags | Days–weeks; remove after rollout | New checkout page |
| Ops flags / kill switches | Long-lived | Disable search suggestions under heavy load |
| Permission flags | Long-lived | Premium features for paid plans |
| Experiment flags | Duration of the test | A/B test of pricing page layouts |
A simple implementation
You don’t need a platform to start. A config-driven flag:
# flags.py
import os, hashlib
FLAGS = {
"new_checkout": {"enabled": os.getenv("FLAG_NEW_CHECKOUT", "off"), "percent": 10},
}
def is_enabled(flag: str, user_id: str) -> bool:
f = FLAGS.get(flag)
if not f or f["enabled"] == "off":
return False
if f["enabled"] == "on":
return True
# "rollout": stable percentage per user
bucket = int(hashlib.sha256(f"{flag}:{user_id}".encode()).hexdigest(), 16) % 100
return bucket < f["percent"]
if is_enabled("new_checkout", user.id):
return render_new_checkout()
return render_old_checkout()
Hashing the user ID keeps each user’s experience consistent across requests. Changing environment variables still needs a restart. Storing flags in a database or Redis makes them changeable live.
Flag platforms (self-hosted options)
| Tool | Notes |
|---|---|
| Unleash | Open source, mature; SDKs for most languages; strategies for gradual rollout, user IDs, environments |
| Flagsmith | Open source; flags plus remote config; UI and API |
| GrowthBook | Open source; strong on experiments and analytics |
| OpenFeature | Vendor-neutral SDK standard, so you can switch providers later |
They run in Docker with PostgreSQL on a small VPS. Keep the admin UI behind authentication, or a VPN. See Docker Compose in production.
Gradual rollout playbook
- Deploy with the flag off. Verify that nothing changed.
- Enable it for internal users (team accounts).
- Enable it for 1–5% of users. Watch error rates, latency, and business metrics.
- Increase in steps (10%, 25%, 50%, 100%).
- If anything goes wrong, turn it off. No rollback deploy needed.
- After full rollout, remove the flag and the old code path.
Monitoring makes this work. See observability basics and deployment strategies.
Avoid flag debt
Flags left in the code forever make it hard to read and test:
- give every release flag an owner and an expiry date;
- remove release flags within weeks of 100% rollout;
- keep the number of active flags visible (platform dashboards help);
- test both paths while a flag exists.
Flags and databases
Flags don’t make schema changes reversible. Combine them with expand and contract migrations: add new columns first, switch code via the flag, and remove old columns later. See deployment strategies.
Quick reference
| Goal | Flag approach |
|---|---|
| Hide unfinished work | Release flag, default off |
| Gradual rollout | Percentage by hashed user ID |
| Emergency off switch | Ops flag / kill switch |
| Plan-based features | Permission flag |
| Tools | Unleash, Flagsmith, GrowthBook, OpenFeature |
Related: CI/CD pipeline explained and blue-green deployment.