Feature Flags in Swift: How to Enable Features Without a New Build and Not Break Production
Imagine: you rolled out a new dark theme, and users are writing that the text has become unreadable. Or you added a complex recommendation algorithm that lags on older iPhones. In the classic scenario, you fix the code, build, go through review, wait for the App Store — a day or more. All this time, the bug lives in production.
Feature flags (or feature toggles) is a technique that allows you to enable and disable application functionality without deploying new code. Essentially, these are conditional operators that check the flag's value, and the value is stored remotely — on a server or in Remote Config. One toggle — and the feature either appears for everyone or disappears in seconds.
In this article, we'll look at how to implement feature flags in Swift, which patterns to use, and how not to shoot yourself in the foot. Let's go!
Why Feature Flags Are Needed in iOS Development
Many people think feature flags are only for A/B tests. In reality, there are many more scenarios:
- Canary releases — enable a new feature for 5% of users, monitor crashes, and if everything is OK, roll it out to everyone.
- Kill switches — if something goes wrong, you simply disable the functionality remotely, without rolling back the build.
- Gradual rollout — show the feature first to iOS 17+, then to older versions.
- Paid features — hide premium functions behind a subscription, managing flags on the backend.
- Experiments and A/B tests — test hypotheses on real users without creating separate branches.
This is especially relevant for Swift projects, where the binary is built once and then sits in the store. Without feature flags, you won't be able to quickly respond to problems — only through a new release.
Feature Flags Architecture in Swift
A simple implementation is a dictionary [String: Bool] that comes from the server. But in a production project, it's better to create a proper abstraction. Here are the key components:
- FeatureFlagProvider — data source (Remote Config, your API, local file).
- FeatureFlagManager — a singleton or service that caches flags and provides them to views.
- FeatureFlagKey — an enum with typed keys to avoid scattering strings throughout the code.
Let's write a simple but working implementation in Swift.
Step 1. Define an enum with keys
enum FeatureFlagKey: String, CaseIterable { case darkMode = "dark_mode" case newOnboarding = "new_onboarding" case premiumAnalytics = "premium_analytics" case experimentalFeed = "experimental_feed"}Using an enum instead of strings ensures you won't make a typo in the flag name. CaseIterable will be useful for a debug screen.
Step 2. Provider — get flags from Firebase Remote Config
import FirebaseRemoteConfig
final class RemoteConfigProvider: FeatureFlagProvider { private let remoteConfig = RemoteConfig.remoteConfig() func fetchFlags(completion: @escaping ([String: Bool]) -> Void) { remoteConfig.fetch(withExpirationDuration: 3600) { status, error in if status == .success { self.remoteConfig.activate { _, _ in let flags = FeatureFlagKey.allCases.reduce(into: [String: Bool]()) { result, key in result[key.rawValue] = self.remoteConfig.configValue(forKey: key.rawValue).boolValue } completion(flags) } } else { print("Failed to fetch remote config: \\(error?.localizedDescription ?? "")")