Mobile Feature Flags

Production engineering guide for mobile feature flags covering patterns, implementation strategies, and operational best practices.

Feature flags are a foundational technique in mobile engineering, enabling teams to release new functionality to users without deploying new app versions. They decouple code deployment from feature rollout, allowing gradual, targeted, and reversible releases across device types, user segments, and geographic regions. They matter most when shipping frequently, when rollout complexity exceeds the cost of a full release cycle, and when feature visibility or stability must be validated in production before broad availability.

Feature Flag Lifecycle

A mobile feature flag begins as a named toggle in the codebase, activated by a configuration lookup at app startup or at runtime. The flag is evaluated on every user action or at regular intervals, and its value determines whether a feature is enabled. The lifecycle spans creation, configuration, activation, monitoring, and deactivation.

Creating a Flag

To define a new feature, engineers add a flag name and default value in the app’s configuration system. For example, in an iOS app using UserDefaults and a centralized configuration service:

// Feature flag key in the app's feature flags system
let featureFlags = [
    "new_onboarding_flow": true,
    "dark_mode": false,
    "in_app_purchase_promo": true
]

The flag is then consumed at runtime:

if FeatureFlag.isFeatureEnabled("new_onboarding_flow") {
    showNewOnboarding()
} else {
    showLegacyOnboarding()
}

The flag key is a string, typically in snake_case, and corresponds to a single entry in a backend configuration store. The default value is used when no override is present.

Configuring Flags

Flags are configured through a backend system that stores their state across environments. The configuration is typically a JSON document served to the mobile app on startup or retrieved via a lightweight API.

A typical configuration endpoint returns:

{
  "app_version": "1.2.3",
  "feature_flags": {
    "new_onboarding_flow": {
      "enabled": true,
      "variant": "A",
      "conditions": [
        {
          "type": "user_property",
          "key": "country",
          "operator": "equals",
          "value": "US"
        },
        {
          "type": "device",
          "key": "platform",
          "operator": "in",
          "value": ["iOS", "Android"]
        }
      ]
    },
    "dark_mode": {
      "enabled": false,
      "variant": "dark_theme_v2",
      "conditions": [
        {
          "type": "user_property",
          "key": "premium_user",
          "operator": "equals",
          "value": true
        }
      ]
    }
  }
}

The conditions field enables conditional flag evaluation. Each condition is a key-value predicate that evaluates to true or false. The flag is enabled if all conditions in the list evaluate to true.

Conditions support:

Evaluating Flags at Runtime

The mobile app evaluates flags on startup and when the user navigates to key screens. Evaluation is usually cached in memory and refreshed periodically.

Evaluation logic is stateless and idempotent. For example, in Android using a FeatureFlagManager:

val flag = FeatureFlagManager.getFlag("in_app_purchase_promo")

if (flag.isEnabled(context = appContext, user = currentUser)) {
    showPromoBanner()
}

The isEnabled method checks:

  1. The flag’s global enabled state.
  2. Applies conditions in order, evaluating each.
  3. Returns true if all conditions pass.

If a condition fails, the flag is disabled even if the global state is true.

Managing Flag Variants

Flags support variants to enable different feature behaviors. A variant is a named configuration tied to a flag, allowing A/B testing or progressive rollout.

For example, to enable a new onboarding flow with two variants:

"new_onboarding_flow": {
  "enabled": true,
  "variant": "A",
  "variants": {
    "A": {
      "title": "Welcome to Our App!",
      "steps": [ "intro", "profile", "settings" ],
      "animation": "fade"
    },
    "B": {
      "title": "Hello, New User!",
      "steps": [ "quick", "onboarding", "finish" ],
      "animation": "slide"
    }
  }
}

The app reads the variant and applies its configuration:

let variant = FeatureFlag.getVariant("new_onboarding_flow")
switch variant {
case "A":
    configureFadeAnimation()
case "B":
    configureSlideAnimation()
default:
    configureDefaultAnimation()
}

Variants are evaluated at runtime and can be updated independently of the flag’s enabled state.

Flag Rollout Strategies

Rollouts are controlled through configuration and can be staged, time-based, or user-based.

A staggered rollout might be defined in a JSON object:

"rollouts": [
  {
    "flag": "in_app_purchase_promo",
    "strategy": "percentage",
    "percentage": 25,
    "conditions": [
      {
        "type": "user_property",
        "key": "first_purchase",
        "operator": "equals",
        "value": true
      }
    ],
    "start_time": "2024-06-15T00:00:00Z",
    "end_time": "2024-07-15T23:59:59Z"
  },
  {
    "flag": "dark_mode",
    "strategy": "user_segment",
    "segments": [
      "premium_users",
      "ios_users"
    ]
  }
]

Rollouts are evaluated at app startup and can be combined. For instance, a user might be in both a premium_users segment and receive a 25% rollout, resulting in a compound eligibility.

Monitoring and Observability

Feature flags are not just a deployment mechanism—they are observability tools. Every flag evaluation is logged, and key events are sent to analytics systems.

Each flag evaluation produces a log entry:

{
  "timestamp": "2024-06-15T10:32:15.123Z",
  "user_id": "u-12345",
  "feature_flag": "new_onboarding_flow",
  "enabled": true,
  "variant": "A",
  "conditions": {
    "country": "US",
    "platform": "iOS",
    "premium_user": true
  },
  "evaluation_result": "all_conditions_met"
}

This log is ingested into a streaming analytics system (e.g., Apache Kafka or Amazon Kinesis) and used to monitor:

A missing or delayed flag evaluation can be detected via a dashboard that shows:

Flag Rollback and Deactivation

When a feature regresses or fails, teams roll back using the same flag system.

To roll back a flag:

  1. Set enabled: false on the flag.
  2. Optionally, revert to a previous variant.
  3. Trigger a configuration refresh.

Rollback is immediate and affects all users, with no need to build or deploy a new app version.

Deactivation of a flag is a long-term process. Once a feature is stable and widely adopted, the flag is removed from the codebase and configuration.

A typical deactivation process:

  1. Set enabled: false on the flag.
  2. Monitor usage for 2–4 weeks.
  3. Verify that no users rely on the flag’s behavior.
  4. Remove the flag key from the app’s code and configuration.
  5. Archive the flag’s history in a database or log.

Common Failure Modes

Flag evaluation fails silently when a condition key does not exist in the user data. For example, a condition user_property: "user_level" fails if the user has no user_level field, even if user_level: 5 is present. The app assumes the condition is true only if the key exists and evaluates to true, 1, "true", or 1.0.

Variant resolution fails when a variant is missing. If the flag new_onboarding_flow has variant: "C", but variants.C is not defined, the app defaults to the default variant or logs an error.

Configuration fetch fails during app startup. If the app cannot reach the configuration endpoint, it uses a cached version from the previous session. If the cached version is stale, users may see outdated feature behavior.

Flag evaluation is too slow when the app waits on a network call to fetch the configuration. A 500ms delay in flag evaluation can be noticeable on slow networks, especially during onboarding.

Multiple flag configurations are layered—a global config, an environment-specific config, and a user-specific config. If a user-specific config has a flag with enabled: true, but the environment config has enabled: false, the user-specific config wins. The merge order must be documented and tested.

Debugging and Development

During development, engineers use a feature-flag-debugger tool that logs every flag evaluation to the console and provides a live view of current flag values.

A typical debug log entry:

[FeatureFlag] Evaluating 'dark_mode' for user u-12345
  → Global: enabled = false
  → Segment: 'premium_users' → enabled = true
  → Conditions: [premium_user = true, platform = iOS] → all true
  → Final: enabled = true, variant = 'dark_theme_v2'

The debugger includes:

Best Practices

Feature flags are not a one-off setup. They are an ongoing practice: every new feature is a flag, every rollout is a configuration change, and every failure is a feedback loop. They enable mobile teams to ship faster, react to users, and iterate continuously—without the friction of app releases.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.