androidengineers.Book a session

Scalable Architecture

Exercise: Feature Flag Rollout Plan

exercise45 minHard

Design and implement a complete feature flag system for a new "AI-powered article summarization" feature, including the rollout plan and kill switch.

The Feature

You're shipping an "AI Summary" button on article detail screens. When tapped, it generates a 3-sentence summary using an AI API. The API is expensive and may have reliability issues.

Rollout requirements:

  1. Internal testing: company employees only
  2. Beta: 5% of users
  3. Production: 25% → 50% → 100% over 3 weeks

Step 1: Firebase Remote Config Setup

In Firebase Console → Remote Config → Create parameters:

Parameter: ai_summary_enabled
Default value: false
Description: "Controls AI article summarization feature"

Conditions (in priority order):
1. "Internal testers" → user property: tester_group = "internal" → value: true
2. "5% beta" → random percentile 0–5% → value: true
3. Default → false

Step 2: Implement FeatureFlags Interface

interface FeatureFlags {
    val isAiSummaryEnabled: Boolean
    val aiSummaryMaxLength: Int
    val aiSummaryModel: String  // allows switching AI model remotely
}

class FirebaseFeatureFlags(
    private val remoteConfig: FirebaseRemoteConfig
) : FeatureFlags {
    override val isAiSummaryEnabled
        get() = remoteConfig.getBoolean("ai_summary_enabled")
    override val aiSummaryMaxLength
        get() = remoteConfig.getLong("ai_summary_max_length").toInt().coerceIn(50, 500)
    override val aiSummaryModel
        get() = remoteConfig.getString("ai_summary_model").ifBlank { "gpt-4o-mini" }
}

Step 3: Use Flags in UI

@Composable
fun ArticleDetailScreen(
    article: Article,
    viewModel: ArticleViewModel = hiltViewModel(),
    flags: FeatureFlags = get()
) {
    var summaryState by remember { mutableStateOf<SummaryState>(SummaryState.Idle) }

    Column {
        ArticleContent(article)

        if (flags.isAiSummaryEnabled) {
            AiSummarySection(
                state = summaryState,
                onSummarize = {
                    summaryState = SummaryState.Loading
                    viewModel.summarize(
                        article,
                        maxLength = flags.aiSummaryMaxLength,
                        model = flags.aiSummaryModel
                    )
                }
            )
        }
    }
}

sealed class SummaryState {
    object Idle : SummaryState()
    object Loading : SummaryState()
    data class Success(val summary: String) : SummaryState()
    data class Error(val message: String) : SummaryState()
}

Step 4: ViewModel with Rate Limiting

The AI API is expensive — add client-side rate limiting:

class ArticleViewModel(
    private val repository: ArticleRepository,
    private val aiService: AiSummaryService,
    private val flags: FeatureFlags
) : ViewModel() {
    private var lastSummarizeTime = 0L
    private val minSummarizeIntervalMs = 30_000L  // 30 seconds between requests

    fun summarize(article: Article, maxLength: Int, model: String) {
        val now = System.currentTimeMillis()
        if (now - lastSummarizeTime < minSummarizeIntervalMs) {
            _summaryState.value = SummaryState.Error("Please wait before requesting another summary")
            return
        }

        viewModelScope.launch {
            _summaryState.value = SummaryState.Loading
            try {
                val summary = aiService.summarize(article.body, maxLength, model)
                lastSummarizeTime = System.currentTimeMillis()
                _summaryState.value = SummaryState.Success(summary)
            } catch (e: Exception) {
                _summaryState.value = SummaryState.Error("Summary unavailable")
            }
        }
    }
}

Step 5: Kill Switch Plan

If the AI API goes down or generates harmful content:

1. Open Firebase Console → Remote Config
2. Set ai_summary_enabled = false for ALL conditions
3. Click "Publish changes"
4. Changes propagate within fetch interval (~1hr)
   → OR force-fetch from app: remoteConfig.fetch(0).await() in critical path
5. All users see the button disappear without an app update

Step 6: Monitoring During Rollout

Track:

  • ai_summary_requested — count per user per day
  • ai_summary_success_ratesuccess / (success + error)
  • ai_summary_p95_latency — should be < 3 seconds
  • Crash rate on ArticleDetailActivity — must not increase
// Log analytics events
fun summarize(article: Article, ...) {
    analytics.logEvent("ai_summary_requested", bundleOf(
        "article_id" to article.id,
        "model" to flags.aiSummaryModel
    ))
    // ...
    // On success:
    analytics.logEvent("ai_summary_completed", bundleOf(
        "latency_ms" to elapsedMs,
        "model" to flags.aiSummaryModel
    ))
}

Rollout Milestones

WeekConditionFlag valueAction
0Internal testerstrueVerify no crashes or OOMs
15% of userstrueMonitor error rate < 5%
225% of userstrueCheck P95 latency < 3s
350% of userstrueVerify cost within budget
4100%trueRemove conditional, make permanent

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Exercise: Feature Flag Rollout Plan | Android System Design | Android Engineers