What Is a Feature Flag? A Solo Dev's Plain-English Guide
Summary
A feature flag (or feature toggle) is a conditional in your code whose value is controlled outside the code, so you can switch a feature on or off without redeploying. That separates deploying from releasing. It allows daily merges of unfinished work, gradual rollouts to 1% of users and instant kill switches. The catch: every flag adds a code path and must be deleted after rollout. Solo devs rarely need a vendor on day one.
What is a feature flag? An if statement whose answer lives outside your code. You ship the new code path switched off, then turn it on later by changing a value in a config file, a database row or a flag service, with no redeploy. That one trick separates deploying code from releasing a feature, which is why a flag is the cheapest rollback you will ever own.
I learned the second half of that sentence the hard way. My first flag was a boolean in an environment variable, flipped on a Friday night, and I forgot it existed for eleven months. Nothing broke. That is exactly the problem, and we will get to it.
What does a feature flag actually look like in code?
Smaller than the vendor homepages suggest. Strip away the dashboards and a flag is a lookup plus a branch:
const flags = { newCheckout: false };
if (flags.newCheckout) {
return renderNewCheckout(user);
}
return renderOldCheckout(user);Everything else is a question about where flags comes from and who can change it. A constant in the file is a flag you change by deploying, which defeats the point. An environment variable changes on restart. A row in your database changes in seconds. A hosted service changes in seconds and also gives you per-user targeting, a UI and an audit log.
Here is what snags in practice: the code is trivial, the lifecycle is not. Two branches in production means two behaviours to test, two paths to debug at 2 a.m. and one of them destined to rot.

Which four types of feature flag exist, and which one do you need first?
Martin Fowler's taxonomy of feature toggles is still the cleanest way to sort them, and the useful axis is how long each type should live.
Release flags hide unfinished or risky work. Transitory: a week or two, then delete. This is the one you need first.
Ops flags are kill switches. You wrap a heavy or fragile feature (a recommendation widget, a third-party call) so you can shut it off under load without a deploy. Most are short-lived, a few stay forever and that is fine.
Experiment flags split users into cohorts for A/B tests. They live as long as it takes to reach a statistically meaningful result, so hours to weeks, and they need real traffic to mean anything.
Permission flags gate features by plan or account: the Pro tier, the beta group. Those can live for years, and at that point they are not really flags any more. They are entitlements, and they deserve a proper data model.
If you are a solo dev with 40 users, you need release flags and maybe one kill switch. Experiment flags on 40 users produce noise, not data. Skip them until you can name the metric and the sample size.
What problem do flags solve that branches and deploys don't?
Three things, and only three are worth caring about.
Shipping unfinished work safely. Long-lived branches hurt because merging them is a small war. A flag lets you merge to main every day with the new code dark. This is the engine behind trunk-based development, where everyone commits to one branch and the flags carry the unfinished parts.
Gradual rollout. Turn the feature on for yourself, then for 1% of users, then 10%, then everyone. Fowler describes picking a canary cohort of roughly 1% by taking a user ID modulo 100. If something is wrong, the blast radius is a handful of people instead of your whole base.
Fast undo. Rolling back a deploy means a build, a pipeline and some nerve. Flipping a flag means one edit. On a bad day the difference is minutes of pain versus an hour.
None of this needs a vendor on day one. All of it needs discipline.
Do you need LaunchDarkly, or is a JSON file fine?
A JSON file or a database table is fine until one of these becomes true: a non-developer needs to flip flags, you need percentage rollouts or per-user targeting, you need an audit trail of who changed what, or you run more than one service reading the same flags.
Here is the rough ladder I would climb, in order:
Constants and environment variables. Zero cost. Fine for the first release flag on a side project.
A
flagstable or a JSON file you read at runtime. Ten lines of code, instant toggles, no vendor. This carries a solo project a surprisingly long way.An open-source or hosted flag service. When you want targeting rules, gradual rollouts and a UI someone else can use.
An enterprise platform. When compliance, approvals and dozens of engineers enter the picture.
Hosted tools cover steps 3 and 4. LaunchDarkly is the incumbent, with the deepest targeting and governance features and a price that reflects it.
Statsig bundles flags with experimentation and product analytics, which makes sense if you intend to measure what the flag did and not only toggle it.
Skip both if your whole team is you. A table with three columns beats a dashboard you open twice a quarter. Pick a service only when the manual version has cost you something specific, like a late-night edit to production config that you regretted.
One piece of insurance worth knowing about: OpenFeature is a CNCF incubating project that defines a vendor-neutral API for evaluating flags, with SDKs for Node.js, Python, Go, Java, .NET and web clients, among others. You code against the standard interface and swap the provider underneath. If you suspect you will outgrow your first setup, wiring it through OpenFeature makes that move a config change.

How do you add your first flag without making a mess?
Start with a single release flag and a few ground rules. This is the version I wish I had followed.
Name it for the feature, not the mechanism. new-checkout-flow tells you what breaks if it is wrong. flag_47 tells you nothing in six months.
Default to off, and make off mean the old behaviour. The convention that "off equals legacy, on equals new" is what lets you reason about the system under pressure. Break it and every flag becomes a puzzle.
Evaluate in one place. Fowler calls the fix toggle inversion: instead of sprinkling if (flags.x) across twenty files, decide once, near the edge of the system, and pass the result in. One decision point means one place to change and one place to test.
Write the removal ticket the moment you create the flag. Put it in the backlog with a date. Fowler also suggests expiry dates on short-lived toggles and even tests that fail once a flag passes its date. A flag without a removal ticket is a loan with no repayment schedule.
Log every evaluation that matters. When a user reports a bug you want to know which side of the flag they were on. If you cannot answer that, the flag is a liability.
Testing is where people over-engineer. You do not need to test every combination of flags, because most flags do not interact with each other. Test the configuration you expect in production, test the fallback with the new flag off, and optionally run one all-on pass to catch drift.
What is the real cost of feature flags nobody mentions?
Every flag doubles a code path. Ten independent flags is, in theory, 1,024 combinations, and while you will never test them all, you can still hit one in production that nobody anticipated.
The more painful cost is dead flags. The rollout finishes, everyone is on the new behaviour, the flag stays at 100%, and the old branch sits in the repo as code nobody dares delete. Six months later no one remembers whether turning it off is safe.
The famous version of this is Knight Capital in August 2012. An old flag that had once activated a retired trading function was reused for new code. One of eight servers missed the deployment and still ran the old code. In 45 minutes the firm lost roughly $440 million by its own quarterly filing, a figure later reports put closer to $460 million. That is a trading firm with real money and real engineers, so I will not claim your side project is safer. The lesson scales down fine: never recycle a flag name, and delete dead flags before they become archaeology.

Fowler's framing is the one I keep: treat flags as inventory with a carrying cost. Some teams cap the number of live flags and require removing one before adding another. For a solo dev, a hard ceiling of five is plenty.
When should you skip feature flags entirely?
More often than the tooling vendors admit.
Skip them when the feature is small, the change is easy to revert in git and your deploy takes two minutes. A flag adds a branch, a config source and a thing to forget. If a revert is faster than a flag flip, the flag is theater.
Skip them when you have no users yet. Gradual rollout to zero people is just a delay. Ship, look at the logs, fix.
Skip them for database migrations that cannot be undone by a toggle. A flag can hide a new column in the UI, but it cannot un-drop a table. Schema changes need their own expand-and-contract plan, flags or not.
Worth it when: the change touches payments or auth, when you want to merge daily with unfinished work, or when a third-party dependency might fail and you want a kill switch.
What do you need to watch once a flag is live?
A flag controls exposure, and exposure only helps if you can see what happens next. Turning a feature on for 10% of users without watching error rates and latency is a coin flip with extra steps.
Before the first rollout, make sure you can answer three questions in under a minute: are errors up for the flagged cohort, is latency worse, and did anything downstream change? Uptime monitoring and log search cover most of this for a small product, and a tool like Better Stack puts monitors, status pages and logs in one place with a free tier.
Here is the rule I follow now: a flag is only as good as the alarm attached to it. Without one, you are turning things on in the dark.
What would we actually do this week?
Pick one unfinished thing you are afraid to merge. Put it behind a boolean read from a table or JSON file, default off, and merge it to main today. Write the removal ticket before you close the editor, with a date two weeks out.
Then turn it on for yourself only, and use it for a day. If it holds up, widen it. If not, flip it off and you have lost nothing but a minute.
Which flag in your codebase right now has been at 100% for more than a month? That one is your first deletion.