What it is and why it matters
A feature flag is a yes or no that the app reads while it runs. If it's on, the user sees the new feature. If it's off, they see the old one. The code for both paths is already inside the app: the flag only picks which one to use.
The problem it solves is simple. You ship a feature, something breaks, and to remove it you need another change, another deploy, maybe another wait for store review. With the flag you change one value and the feature disappears. Then you fix it calmly.
If you build with Claude Code it matters even more: you ship often, and you haven't always read every line. Being able to switch off what you just shipped, without touching the code, takes a lot of stress out of release day.
1. Where I keep them
I use a “flags” table in the database the app already uses: one row per flag, with name, on yes or no, percentage and a list of users. The app reads it at startup and every so often while it's open. I change a value and the feature turns on or off with no new version. Whoever already has the app open gets the change at the next read of the flags.
The only real condition is this: the value has to live somewhere you can change without a deploy. There are three main options.
- A table in the database. That's what I do: nothing new to learn, and you can change it from a page of your own.
- A service dedicated to feature flags or remote config. It gives you a ready-made dashboard, percentages and stats. To get started, the table is enough.
- An environment variable. Fine on the server, but on many hosts changing it requires a new deploy or a restart: check how your host applies it. In a mobile app it usually stays fixed in the build.
First I ask Claude Code for the table and a single place in the code that every question about flags goes through, instead of twenty scattered checks. With Claude Code, everything that follows takes a few minutes to set up.
Create a flags table with name, on yes or no, percentage and a list of users, plus one single function the app uses to know whether a flag is on for the current user.
2. The feature is born switched off
Every new feature enters the code already behind its own flag, switched off.
Put feature X behind a flag called new-x, off by default, read from the flags table.
Then I ship it like that. Nothing changes for users: the new code is there, but it doesn't run. This is the first of the three rules: no value in the database can make a feature appear if it isn't in the installed app.
On iPhone apps this is the second rule, and Apple spells it out: new features must be described in the review notes and be accessible to the reviewer, and hidden ones are forbidden. If you ship a feature switched off, say so in the notes and explain how to see it.
3. Turning it on in stages
I never turn it on for everyone at once. First for me, then for about ten users, then for everyone.
Make it possible to turn new-x on for a list of users and for a percentage, always the same for the same user.
The “always the same for the same user” part matters. If the app rolls the dice every time it opens, the same person sees the feature today and not tomorrow. Usually you compute a fixed number from the user's id: if the number falls below the percentage, the feature is on. That way, when you raise the percentage, whoever already had it keeps it.
Between one stage and the next I look at the errors and at what users write to me. If everything is quiet, I go up.
4. If reading fails, everything stays off
The flag is already a kill switch: if the feature causes trouble, I switch it off and the old version comes back. But there's a case nobody thinks about: what if the app can't read the flags? Slow database, no connection, a table renamed by mistake.
If reading the flags fails, treat every flag as off and show the old version.
That way, if reading fails, the app shows the version that already worked on its own. Turning everything on when it doesn't know is the fastest way to show everyone a half-finished feature.
Then there's the delay: whoever has the app open sees the feature until it rereads the flags, and dedicated services usually cache the values too. How long I have to wait, I ask.
Tell me how long it takes, in the worst case, for a user who already has the app open to see a flag I've just switched off, and where that time is set.
5. Only I change the table, and from my phone
So I don't have to open the database from my computer, I use an admin page from my phone. But protection comes before the page: hiding a button isn't enough. Who can modify the table has to be decided by the server, otherwise someone clever turns your features on and off without going through the page. That's why the prompt does the two things in this order.
Protect the flags table on the server: the app can read it, but only my account can modify it. Then create an admin page, just for me, with a toggle for each row.
6. Cleaning up
A flag that's been on for everyone for weeks, and works fine, isn't needed anymore. Left there, it's old code nobody tests, and a row someone will sooner or later switch off by mistake. On top of that, with the rule from step 4, if reading fails the app falls back to the old version even for a feature everyone uses.
List the flags that have been on for everyone for more than a month and remove them from the code, leaving the feature on.
Before shipping, I check that the feature stays on and remove the row from the table too.
The three rules
- The flag only turns on code that's already in the published version: the feature has to be published switched off, first.
- Switching off code that's already published doesn't require review; but the stores don't allow hidden features to be turned on later without review. Flags are for switching off and for turning on in stages, not for sneaking new features past review.
- Nothing secret in the flags: the app reads them, so anyone who wants to can see them.
The name matters too: “secret-partner-discount” already says too much. Keys and passwords stay on the server.
Common mistakes
- Turning the flag on before shipping the feature: nothing happens, because the code isn't there. Ship it off first, then turn it on.
- Treating a flag that can't be read as on: if reading fails, the right value is off.
- Rolling the dice on every launch: the percentage must always give the same result for the same user.
- Protecting only the admin page: the table itself must accept changes only from your account.
- Never removing old flags: the ones that have been on for everyone for over a month should come out of the code.
Checklist
- Flags table created, one single function to read it
- The new feature is shipped switched off
- Percentage stable for the same user
- If reading fails, everything off
- I know how long it takes to switch off for people with the app open
- Only my account can change the flags
- Every so often I remove flags that have been on for over a month
Frequently asked questions
Is it the same as rolling back to the previous version?
No. Rolling back puts the whole app back the way it was, and on a mobile app that means another version and another review. The flag switches off only that feature, and everything else stays as it is.
Does it work on an app that's already in the store?
Yes, and that's where it helps most, because every new version goes through review and not everyone updates right away. But it only works for code already inside the installed version: a flag can't add a feature the app doesn't have.