<FlutterSolution/> flutter · dart · clean architecture

Flutter Interview Prep #22 Feature Flags, Remote Config & Progressive Delivery: Shipping Risk, Not Just Code

Feature Flags, Remote Config & Progressive Delivery: Shipping Risk, Not Just Code

A junior developer thinks about how to ship a feature. A senior developer thinks about how to turn it off when it breaks at 2 AM.

01. The Mobile Trap: Releases Aren't Reversible

If you deploy a bug to a backend web service, you can usually revert the commit and push a fix within minutes. Mobile development does not have this luxury.

Once a broken Flutter app is in users' hands, you are trapped by the ecosystem. Fixing it requires cutting a new build, submitting it to Apple and Google, waiting for App Store review, and then waiting for users to actually download the update. A critical bug in a core flow—like a completely redesigned checkout screen in a shopping app—can bleed revenue for days.

Feature flags and progressive delivery exist to solve this exact structural problem. They answer a critical question: How do you ship sweeping changes without betting your entire user base on a single all-or-nothing release?

The Mitigation Speed Hierarchy

Tap/hover a row to see why seniors prefer the top option.

1. Remote Kill Switch (Feature Flag) Near-Instant
Flipping a flag off in a dashboard updates the app's runtime behavior immediately upon the next fetch. No store review required.
2. Halt App Store Staged Rollout Fast (Hours)
Stops *new* users from getting the bad binary, but users who already downloaded it are stuck with it until a patch is released.
3. Emergency Hotfix Release Slowest (Days)
Requires coding the fix, rebuilding, App Store/Play Store review delays, and waiting for users to manually update.

02. Feature Flags & Remote Config Mechanics

A feature flag (often managed via services like Firebase Remote Config or LaunchDarkly) allows your app to check, at runtime, whether a specific code path should be executed.

Imagine your team spent a month writing a modernized checkout flow. Instead of overwriting the old flow, you ship the app with both flows intact. The app fetches a configuration JSON from your backend, caches it locally, and reads a boolean value.

Dart
Future<void> handleCheckoutTap() async {
  // 1. Check the flag value. 
  // Crucially, always provide a hardcoded safe default (false)
  // in case the fetch fails or the user is offline.
  final useNewCheckout = remoteConfig.getBool(
    'enable_v2_checkout', 
    defaultValue: false,
  );

  if (useNewCheckout) {
    navigator.push(V2CheckoutRoute());
  } else {
    navigator.push(LegacyCheckoutRoute());
  }
}

03. Progressive Delivery (Percentage Rollouts)

Remote config is not just an on/off switch; it is a dial. Progressive delivery is the practice of turning that dial up slowly.

If you flip the new checkout flow on for 100% of your users on Monday morning, and there is a subtle race condition in the payment API, you have broken the app for everyone. Instead, you enable the flag for 5% of users.

This limits the "blast radius." You monitor your crash analytics and payment success rates for that 5%. If things look stable, you dial it up to 25%, then 50%, then 100%.

Flag Assignment: enable_v2_checkout
5%

04. The Kill-Switch Pattern

The inverse of the staged rollout is the kill switch. If your 25% rollout reveals that the new checkout flow is double-charging a specific subset of Android devices, you do not panic, and you do not cut a hotfix branch.

You log into your remote config dashboard and flip `enable_v2_checkout` back to 0%. The next time the app fetches the config (or receives a silent push notification triggering a refresh), it falls back to the legacy checkout code that you purposefully left in the binary.

This is why senior engineers advocate for feature flags: they decouple deployment (submitting the binary to the App Store) from release (exposing the feature to users).

05. Mistake: Store Rollout vs. Kill Switch

⚠️ Common Interview Conflation

When asked how to mitigate release risk, many candidates mix up a Staged Rollout (in the Google Play/App Store console) with a Feature Flag (in the app code). They are completely different layers of protection.

📦
Store-Level Staged Rollout
What it controls Which users receive the newest binary (the .aab or .ipa file).
Reversibility You can halt it, but users who already downloaded the bad binary are stuck with it until a hotfix is approved.
🎛️
App-Level Feature Flag
What it controls Whether a specific feature inside an already-installed binary is active.
Reversibility Instant. Flipping the flag turns the feature off even for users who have the newest binary installed.

06. A/B Testing & Analytics Tagging

A feature flag controls what users see. But if you don't tie that flag to your analytics, it tells you nothing about whether the change actually worked.

If you are rolling out the new checkout flow, you need to know if it improves the conversion rate. To do this, every time the flag is evaluated for a user, that assignment (the "variant") must be attached to subsequent analytics events.

Dart
// Record WHICH version of checkout the user was assigned to
final checkoutVariant = remoteConfig.getBool('enable_v2_checkout') 
    ? 'v2_modern' 
    : 'v1_legacy';

// Tag it as a user property or event parameter
analytics.logEvent(
  name: 'checkout_completed',
  parameters: {
    'order_value': 120.50,
    'checkout_variant_seen': checkoutVariant, // Crucial for attribution
  },
);

07. System Design: Building a Minimal Flag System

In a senior interview, you might be asked to design a rudimentary feature flag system on a whiteboard if third-party services aren't allowed. A robust system requires three conceptual pillars:

  1. Fetch, Cache, Fallback: The app must never block the UI waiting for a network request to check a flag. It should evaluate against a local cache, fetch new values in the background, and always have a hardcoded fallback default if the cache is empty.
  2. Deterministic Bucketing: If a user is in the 5% bucket for the new checkout flow on Tuesday, they must still be in that bucket on Wednesday. You achieve this by hashing a stable identifier (like the User ID or Device ID) combined with the feature key, e.g., `hash(userId + "v2_checkout") % 100`. If the result is < 5, they get the feature. This guarantees consistent assignment without the backend needing to remember every user's state.
  3. Analytics Exposure Logging: The system needs a hook to notify your analytics provider the exact moment a flag is actually evaluated, so you can measure exposure accurately.
🧠 Interviewer's Follow-Up

Expect these pushbacks when discussing feature flags:

  • "Aren't feature flags just technical debt?"
    Answer: "Yes, if left unmanaged. Stale flags clutter the codebase and create untested logic paths. A healthy engineering culture requires a fast-follow ticket to rip out the legacy code and the flag once the new feature hits 100% rollout."
  • "What's the difference between a staged rollout and a kill switch?"
    Answer: "A staged rollout happens in the App Store and controls who gets the new binary. A kill switch is a remote config flag that turns off a specific feature inside an already-installed binary. The kill switch is vastly faster for incident mitigation."

Senior-Level Sample Answer

"If we are replacing the core checkout flow, I would never deploy it as a hard cutover. I would wrap the routing logic in a feature flag checking our remote config service, defaulting to the legacy flow if the network fails. We'd deploy the binary to the App Store, and once approved, we'd enable the flag for just 5% of users. We'd monitor conversion analytics and crash rates tightly. If a critical bug surfaces, we have an instant kill switch: we flip the config back to 0%, immediately routing those users back to the stable legacy flow without needing to wait days for a hotfix to clear App Store review."
✅ Quick Self-Check

Before moving to the next post, answer this out loud: Why is it important to use a hashing algorithm (like `hash(userId) % 100`) for percentage rollouts instead of just generating a random number every time the app opens?

(Answer: Because a random number would cause the user to flip randomly between the old and new features every time they launch the app, causing massive confusion.)

Official Resources

Nachiketa Pandey
// written by

Nachiketa Pandey

Senior Flutter Developer · Cross-Platform & Fintech Applications

Building production Flutter apps at Kotak Neo — clean architecture, BLoC, agentic AI integrations, and fintech-grade security. Sharing everything I learn, one post at a time.

Flutter & Dart Clean Architecture BLoC Firebase FinTech
View Full Portfolio

Comments

Nachiketa
Meet Author// tap to open