Feature Flags and A/B Tests in Mobile Apps
How to use feature flags in a mobile app: flag types, old app versions, offline defaults, kill switches, store rules, A/B tests that work and flag hygiene.
Full-Stack, Mobile & AI Engineer
On the web, a bad release is fixed with a redeploy, and every user has the fix within minutes. In a mobile app, a bad release sits on phones for weeks, because store review takes time and many users don't update. Feature flags give you a switch for code that's already on those phones: turn a feature on for 5% of users, compare it against the old version, or turn it off when it misbehaves.
Used well, flags make mobile releases calmer. Used carelessly, they leave codebases full of dead branches, break old app versions and can even get an app rejected. This guide covers how to use feature flags in a mobile app: the four kinds of flags, what's different on mobile, a safe pattern in code, kill switches, A/B tests that give real answers, choosing a tool and keeping flags tidy.
Key takeaways
- Separate flag types: release flags, experiments, kill switches and long-lived configuration have different lifespans and owners.
- Mobile flags must survive old app versions, offline starts and slow networks, so every flag needs a safe default and cached values.
- Flags switch code that's already shipped; they can't add new code. That's what builds and over-the-air updates are for.
- Don't hide features from App Review. Apple's guideline 2.3.1 forbids hidden, dormant or undocumented features.
- A/B tests need a primary metric, enough users and patience, especially with mobile release cycles.
- Remove flags after rollout. Every flag left in the code is a branch nobody tests.
Four kinds of flags
| Type | Purpose | Lifespan | Example |
|---|---|---|---|
| Release flag | Roll out a finished feature gradually | Days to weeks | New checkout for 10%, then 50%, then everyone |
| Experiment | Compare variants against a metric | Weeks | Two paywall designs |
| Kill switch | Turn off a risky feature or integration fast | Long-lived | Disable a third-party SDK that's crashing |
| Configuration | Tune values without a release | Long-lived | Upload size limit, minimum supported version |
Mixing these up causes a lot of flag mess: experiments that never end, release flags that become permanent and kill switches nobody has ever tested.
What's different on mobile
- Old app versions live on. A flag that turns on a feature must only reach app versions that contain it. Target flags by app version as well as by user.
- Apps start offline. Every flag needs a sensible default in the code, plus the last known values cached on the device, so the app works on a plane.
- Flicker is visible. If flags load after the first screen renders, users see one version and then another. Load flags early, cache them, and decide on the first screen which values to trust.
- Releases are slow. You can't quickly add a missing flag, so put flags around risky features before you ship them.
- Store review applies. Apple's guideline 2.3.1 says apps must not include hidden, dormant or undocumented features, and new features must be accessible to App Review. Use flags for gradual rollout and safety, not to switch on functionality reviewers never saw.
Flags versus over-the-air updates
Over-the-air updates (such as EAS Update in Expo apps) ship new JavaScript to installed apps. Feature flags switch between code paths that already exist in the app. They work well together: ship a feature behind a flag in a store build or an update, then use the flag to control who sees it and to turn it off instantly if needed.
A safe pattern in code
Keep your flag vendor behind one small module, with typed defaults, so the rest of the app never depends on the vendor directly and every flag has a fallback:
// flags.ts: the only file that knows which provider you use
const DEFAULTS: { "new-checkout": boolean; "max-upload-mb": number } = {
"new-checkout": false,
"max-upload-mb": 50,
};
type Flags = typeof DEFAULTS;
export function useFlag<K extends keyof Flags>(key: K): Flags[K] {
const value = useProviderFlag(key) as Flags[K] | undefined; // your provider's hook
return value ?? DEFAULTS[key];
}Then in a screen:
const newCheckout = useFlag("new-checkout");
return newCheckout ? <NewCheckout /> : <Checkout />;Three rules keep this pattern safe: defaults are the safe behaviour (usually "off"), values are cached for offline starts, and flag reads happen in as few places as possible, ideally once near the top of a feature.
Kill switches
A kill switch is a flag whose only job is to turn something off: a new feature, a third-party SDK, a payment method, a heavy animation. They're cheap insurance for anything risky. Two things make them real rather than decorative:
- Test the off path. A kill switch that crashes the app when flipped is worse than none. Include the off state in your test plan.
- Know who can flip it, and document when to use it, so nobody hesitates during an incident.
From the field: Feature flags sit alongside the rest of the release path I own: EAS Build, Submit and Update, Fastlane and GitHub Actions pipelines, phased rollouts, store review and Sentry crash triage. Flags, phased rollouts and over-the-air fixes together make it possible to limit a bad change to a few users and reverse it quickly.
A/B tests that give real answers
Running an experiment is easy. Getting a trustworthy answer takes discipline:
- Pick one primary metric before you start (trial starts, checkout completion, day-7 retention), plus guardrail metrics that must not get worse (crash rate, refunds, support tickets).
- Estimate the sample size you need to detect a meaningful difference, and how long that will take at your traffic.
- Keep assignment sticky per user, across sessions and devices where possible.
- Log exposure when users actually see the variant, not when the flag is fetched; otherwise you dilute the result with people who never reached the screen.
- Don't stop early because the dashboard looks good on day three. Decide the duration in advance, and run at least one full week to cover weekly patterns.
- Check results by platform and app version, since iOS and Android users often behave differently.
Choosing a tool
| Tool | Strengths | Notes |
|---|---|---|
| LaunchDarkly | Mature flag management, targeting, governance | Enterprise focus |
| Statsig | Flags plus strong experimentation | Experiment analysis built in |
| PostHog | Flags, experiments, analytics and session replay together | Open source, can self-host |
| Firebase Remote Config and A/B Testing | Free, integrated with Firebase | Best if you already use Firebase |
| GrowthBook | Open-source flags and experiments on your own data warehouse | Self-host or cloud |
| Unleash | Open-source flag management | Self-host or cloud |
Check each tool's React Native SDK, offline caching, app version targeting and pricing model before deciding. OpenFeature, a CNCF project, offers a vendor-neutral API if you want to keep switching costs low.
Flag hygiene
- Name flags clearly ("checkout-v2-rollout", not "test-flag-3").
- Give every flag an owner and an expiry date, and review them monthly.
- Remove release flags and finished experiments from the code once they're at 100% or decided.
- Avoid nesting flags inside other flags; the combinations multiply quickly.
- Keep an audit log of who changed which flag and when.
FAQ
What are feature flags in a mobile app?
Feature flags are remote switches that control which code paths an installed app uses, without a new release. They let teams roll out features gradually, run A/B tests, tune configuration and turn off problems quickly, as long as the code for each path already exists in the app.
Does Apple allow feature flags in iOS apps?
Yes, for gradual rollouts, experiments and safety switches. What Apple's guideline 2.3.1 forbids is hidden, dormant or undocumented features: new functionality must be described and accessible for App Review, so don't use flags to hide features from reviewers.
How are feature flags different from over-the-air updates?
Over-the-air updates deliver new JavaScript code to installed apps. Feature flags switch between code that's already there. Teams often ship a feature behind a flag, then use the flag to control who sees it and to disable it instantly if something goes wrong.
How long should a mobile A/B test run?
Long enough to reach the sample size you calculated in advance, and at least one full week to cover weekly patterns. Mobile tests often need longer than web tests because of lower traffic per variant and slower adoption of new app versions.
Next steps
If you want safer mobile releases with feature flags, kill switches and experiments that give trustworthy answers, I can set them up as part of your release process. Get in touch, or see my services.
Sources
- App Review Guidelines (2.3.1), Apple
- Feature Toggles (aka Feature Flags), Martin Fowler
- OpenFeature, CNCF
- Firebase Remote Config, Firebase
- EAS Update, Expo documentation
Related
A Testing Strategy for React Native Apps That Teams Actually Keep
A practical React Native testing strategy: static checks, unit tests, React Native Testing Library v14 component tests, E2E flows and CI that sticks.
Detox vs Maestro: End-to-End Testing React Native in 2026
Detox vs Maestro for React Native E2E tests in 2026: how they work, the same test in both, flakiness, Expo and CI support, and which to choose.
Getting to 99.9% Crash-Free Sessions: A Crash Triage Playbook
React Native app crashing in production? A triage playbook: crash-free metrics, readable stack traces, common causes, prioritising fixes and safe releases.
