Skip to Content
Course content

169: Practice Exercise: Building a Feature Flag System

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've all been there: you've spent two weeks building a shiny new "Premium Dashboard," but the Product Manager is nervous about flipping the switch for everyone at once. You need a way to toggle this feature on or off without submitting a new build to the App Store. The instinct here is to move fast, and when we move fast, we usually reach for the simplest tool available: a global singleton.

The Hardcoded Singleton Trap

In a naive implementation, you might create a FeatureFlags class with a shared instance and a handful of booleans. It looks like this:

class FeatureFlags {
    static let shared = FeatureFlags()
    var isPremiumDashboardEnabled = false
    var isNewOnboardingFlowEnabled = true
    
    private init() {}
}

// Usage in a ViewController
if FeatureFlags.shared.isPremiumDashboardEnabled {
    showPremiumView()
} else {
    showStandardView()
}

At first glance, this is great. It's easy to understand and takes thirty seconds to set up. But as I've seen in a few legacy projects, this approach quickly becomes a liability. The problem isn't the singleton itself, but the lack of abstraction. Your business logic is now tightly coupled to a global state. If you want to test how the app behaves when the dashboard is disabled, you have to manually mutate a global variable, which can lead to flaky tests if you aren't meticulously resetting the state between every single test case.

When Global State Breaks Your Testing

The real pain hits when you realize you can't easily mock these flags. Imagine you're writing a unit test for a DashboardCoordinator. You want to verify that the coordinator routes to the StandardView when the flag is off. With the singleton approach, your test is essentially fighting the rest of the app for control over FeatureFlags.shared. It's a nightmare for parallel testing, and it makes your code rigid. You aren't asking "Does this component behave correctly given this configuration?" instead, you're asking "Does this component behave correctly given the current global state of the entire application?"

Abstracting with Protocols and Enums

The professional way to handle this is to decouple the request for a flag from the source of the flag. I prefer using a protocol-oriented approach combined with a strongly typed enum for the features. This prevents typos that happen when using string keys and allows us to swap the provider depending on the environment.

enum Feature: String {
    case premiumDashboard
    case newOnboarding
}

protocol FeatureFlagProviding {
    func isEnabled(_ feature: Feature) -> Bool
}

class RemoteFeatureFlagProvider: FeatureFlagProviding {
    func isEnabled(_ feature: Feature) -> Bool {
        // In a real app, this would check a local cache 
        // populated by a remote config service (like Firebase)
        return false 
    }
}

class MockFeatureFlagProvider: FeatureFlagProviding {
    var mockFlags: [Feature: Bool] = [:]
    func isEnabled(_ feature: Feature) -> Bool {
        return mockFlags[feature] ?? false
    }
}

Now, instead of reaching for a singleton, you inject the FeatureFlagProviding protocol into your classes. Your view controller no longer knows where the flag comes from; it just knows that it can ask a provider if a specific feature is enabled. This makes your components incredibly easy to test. In your production code, you inject the RemoteFeatureFlagProvider; in your tests, you inject the MockFeatureFlagProvider and set the exact state you need for that specific test case. It's a bit more boilerplate up front, but it saves you from the "global state headache" as the project scales.




📋 Practical Task

Exercise: Implementing a Decoupled Feature Toggle for a 'Dark Mode Preview'

Your task is to move a hardcoded feature flag into a protocol-based system. You are building a "Dark Mode Preview" feature that should only be visible to beta testers.

Requirements:

  • Create a Feature enum that includes a darkModePreview case.
  • Define a FeatureFlagProviding protocol with a method isEnabled(_ feature: Feature) -> Bool.
  • Implement a LocalFeatureFlagProvider that returns true for darkModePreview and false for everything else.
  • Create a SettingsViewModel class that accepts a FeatureFlagProviding instance via its initializer.
  • Add a computed property to SettingsViewModel called shouldShowDarkModeToggle that returns the status of the darkModePreview flag using the injected provider.
  • Instantiate the SettingsViewModel using the LocalFeatureFlagProvider and print the value of shouldShowDarkModeToggle to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.