Feature flags — the challenges

Adrian Coman
6 min readJan 9, 2024

Challenges of Feature flags

Besides solving problems, feature flags have drawbacks as well, they add testing complexity, bloat your code, and can lead to dead code that’s not used anymore.

From our experience at Pago, we discovered 4 main challenges:

  1. Bloated code
  2. Increased testing complexity
  3. Code becomes hard to maintain
  4. Hard to keep track of production flags

1. Bloated code

Bloated code was the first problem we noticed when we started extensively using feature flags. We would add A/B tests and other experiments, and we would forget about them after the experiment was done. We’d let our users stay on the B variant and not remove the A variant from our code block, and this became messy fast.

Luckily this one was easy to fix, each time we created a task for running an experiment we had to decide approximately how long it would last. So before we ship it, we’ll just add an extra task in which we have to remove the unneeded flag at the end of the experiment.

As I said in another post there are four main types of feature flags, and from our experience, we got to the following graph that represents how long a feature flag should last in our code base.

Feature flags duration

When doing research for my talk at DevFest 2022 I saw a similar graph in Martin Fowler’s article about Feature Flags. What we discovered is that each team or organization can have a different timeline and what works for what organization might not work for you. The bottom line is that to keep your code base bloat-free, you need to actively clean up unused feature flags, and this helps with handling testing complexity as well.

2. Testing complexity

Handling QA complexity is a daunting task. I recommend creating a dashboard, either in a browser or directly a dev menu in the mobile app that allows you to control the feature flags.

This will make working with feature flags less frightening and will allow your team to know exactly the different states of feature flags with just a few taps.

Unfortunately, there is no silver bullet to deal with overly complex scenarios, and hoping that the QA team can catch all the possible cases is wishful thinking. Instead of focusing on how to deal with the many scenarios that arise, you should focus on lowering the number of feature flags that you have in the app, documenting where and how each feature flag impacts the app, and cleaning up after a feature flag reaches its goal.

3. Code becomes hard to maintain

By default, when adding feature flags you bring complexity to your code. Finding a way to decrease the complexity, and limit their ramifications is extremely important.

Dealing with sub-flags

In complex scenarios, it’s better to have sub-flags for features, and by turning off the main flag, you cut down access to all the sub-feature flags in the app.

Say you just added a new product to your app that allows you to pay your car insurance (car_insurance_enabled feature flag) and down the road, you have multiple feature flags for finer control over smaller behavior. Let’s say you have a flag for controlling if your user can add a new card for this payment method or use one of the regular cards already added (car_insurance_new_card_enabled feature flag).

Now, let’s say that something happens in your release and you want to turn off access to the whole product, you should be able to turn the car_insurance_enabled flag off and the car_insurance_separate_card_enabled flag should automatically turn off as well. There should be no path in which the first one is OFF and the second one is ON, it should look something like the following (with gray representing the OFF toggled paths of your app).

Feature flags tricking down the complexity

You can achieve this by either logic on the API side or in your app, but you should never turn off 4 or 5 flags just to completely disable a feature.

Dependency inversion principle

One of the hard lessons we learned was that we should keep our feature flag logic hidden as much as we can away from our business logic to decrease the code complexity.

In an Android MVVM architecture, let’s say we have the following 3 states for our screens

Main dashboard screen with 3 different cards

In the ViewModel, we’d have something like this:

class BasicFlagViewModel(
private val featureFlagManager: FeatureFlagManager
): ViewModel() {

private val _activeCampaign = MutableStateFlow<PromoCard?>(null)
val activeCampaign = _activeCampaign.asStateFlow()

fun getCardToDisplay() {
val cardToDisplay = when (featureFlagManager.getCampaign()) {
AvailableCampaigns.VODA -> {
PromoCard(
text = R.string.voda_title,
imageUrl = VODA_IMG,
ctaText = R.string.voda_cta
)
}
AvailableCampaigns.INFOGRAPHIC -> {
PromoCard(
text = R.string.infographic_title,
imageUrl = INFOG_IMG,
ctaText = R.string.infographic_cta
)
}
else -> {
null
}
}

cardToDisplay?.let {
viewModelScope.launch {
_activeCampaign.emit(cardToDisplay)
}
}
}
}

But, our VM shouldn’t care about our feature flags, it should only care about displaying the card or not, so ideally it should look like this:

class InversionViewModel(
private val displayedCard: BasicCard?
): ViewModel() {

private val _activeCampaign = MutableStateFlow<PromoCard?>(null)
val activeCampaign = _activeCampaign.asStateFlow()

fun getCardToDisplay() {
val cardToDisplay = displayedCard?.getCard()
viewModelScope.launch {
_activeCampaign.emit(cardToDisplay)
}
}
}

To achieve this we’ll create an interface for BasicCard:

interface BasicCard {
fun getCard(): PromoCard
}

And the possible states of the card as separate classes:

class VodaCampaignCard: BasicCard {
override fun getCard() = PromoCard(
text = R.string.voda_title,
imageUrl = VODA_IMG,
ctaText = R.string.voda_cta
)
}

class InfographicCard: BasicCard {
override fun getCard() = PromoCard(
text = R.string.infographic_title,
imageUrl = INFOG_IMG,
ctaText = R.string.infographic_cta
)
}

And finally, when instantiating your VM (or whatever part of the app you want to keep your business logic in), you can do something like this:

val viewModel = InversionViewModel(
when (featureFlagManager.getCampaign()) {
AvailableCampaigns.VODA -> VodaCampaignCard()
AvailableCampaigns.INFOGRAPHIC -> InfographicCard()
AvailableCampaigns.NONE -> null
}
)

4. Hard to keep track of production flags

After you’re going to introduce feature flags, you’re going to hit scenarios where you’ll be interested in the feature flags that a user has access to, for this you should have an easy-to-see dashboard that can query the current state of the feature flags in production.

After some more time, you’re going to have more dynamic scenarios, where feature flags can’t just be displayed in a dashboard because each will have different feature flags based on certain conditions. For this, you should add to your dashboard the possibility to query the database for a particular reason.

As the system evolves some more, you’ll realize that maybe the state of a feature flag at a point in time is not represented correctly in the database anymore. You could either introduce logs where you see when every feature flag changes or if you have a lot of feature flags to deal with, introduce snapshots that represent how the feature flag list looked like each time your user opened the app.

Bottom line

Without a doubt, feature flags are powerful tools for software releases and testing out new features even though they come with their challenges such as bloated code, increased testing complexity, and maintenance difficulties.

However, with the right strategies and tools, these challenges can become manageable, especially by implementing good coding practices, adopting efficient tracking systems, and maintaining a disciplined approach to their lifecycle.

The key to successfully utilizing feature flags is to not look at them as just a technical implementation but as a philosophy of delivering and testing software in a controllable way. Embrace their benefits and accept their challenges as they’re a game-changer to your continuous delivery & deployment strategy.

If you enjoyed the read, take a look at another post I wrote about the four main types of feature flags.

Oh and by the way, we’re always looking for new people to join us at Pago. Check out the available jobs here. In case you don’t see a job opening for your role, but you think you might be a good match, drop us an email.

--

--