Feature flags: an alternative to branches
Git branches are often used to write features that take a long time to write. I’m starting to think that they may not be the ideal solution.

A branch might be worked on for weeks then finally merged to the main branch when it’s done. Almost every team I’ve worked on runs into the same issues with long-running branches:
-
They get out-of-sync. Branches will require regularly merging in changes from main. Devs tend to avoid this chore, making things worse.
-
They’re impossible to review. Long-running branches often live as pull requests with thousands of lines.
-
They’re difficult to revert. If any issues arise after merging into production, features may need to be “undeployed.” This is easy with git revert until more code piles up to make it impractical.
What if WIP features can be merged into production? Maybe it can. Feature code in the main branch doesn’t have to be made available in production. Blocks of code can always be turned off with conditional statements.
These conditionals are often called feature flags or feature toggles. I’ll list down a few ways I know of implementing feature flags, starting with the simplest (level 0) to the more advanced (level 7).
if (process.env.NODE_ENV === 'production') {
// ...
}
A feature’s UI can be disabled in production with an if condition. This would allow the app to be deployed even if some features aren’t complete yet.
const isProduction =
process.env.NODE_ENV === 'production'
<div>
{isProduction ? null : <button>Show lyrics</button>}
</div>
<% if Rails.env.production? %>
<button>Show lyrics</button>
<% end %>
-
Testing can be difficult. This makes the new features available in tests, and toggling these in unit tests means having to hijack environment variables somehow.
-
Features also show up in dev mode. Config-based checks are often preferred over environment checks for this reason.
✨ Next, let’s organise mutiple flags with configuration.
Having one switch per feature can be more flexible, not to mention more organised. Rather than relying on NODE_ENV, each environment can have a list of flags that are enabled for it.
Common conventions include environment variables (shown below) or YAML configuration files.
LYRICS_VIEW_ENABLED=0
TRENDING_PAGE_ENABLED=0
LYRICS_VIEW_ENABLED=1
TRENDING_PAGE_ENABLED=1
- URL’s can still be guessed. While it’s often enough to hide links to new features, some new URL routes may also need to be hidden.
URL routes can be disabled in production in the same way. While hiding links will make the feature invisible, that doesn’t prevent users from guessing the URL of new features.
const isProduction =
process.env.NODE_ENV === 'production'
if (!isProduction) {
router.get('/browse/trending', browseByTrending)
}
- Features aren’t available in production. This is often what we want at first, but at some point it would be nice for the internal team to try the feature out in production.
✨ Next, let’s try enabling the feature for a limited set of users.
Features can be limited to certain users such as “admin” users. This allows the team to test out new features in production.
const user = getCurrentUser()
<% if (user.admin) { %>
<a href='/trending'>View trending albums</a>
<% } %>
-
User accounts are required. This makes this method not suitable for features not requiring users to sign in, or sites without authentication.
-
Requires database storage. While it’s easy for some apps to store metadata for a user (eg, an
admin
flag), this might not always be the case.
✨ Next, let’s look at a possible solution that might remove the need for state storage.
Features can be restricted in production using special cookies. Unlike the user-based approach, this allows testing of features that don’t require signing in.
<% if (req.cookies['trending-enabled'] === '1') { %>
<a href='/trending'>View trending albums</a>
<% } %>
-
May be difficult to enable. Developers may know their way around adding new cookies, but non-technical people may need something more user-friendly.
-
Can be prone to tampering. Some users will be able enable features if the cookie names leak out.
✨ Next, let’s try to make this a bit more user-friendly.
A simple page can be used to list all cookie-based flags and provide a UI to enable and disable them.
Major browsers today have a secret page to allow enabling experimental features. Something like this can also be done for web apps, possibly hidden by authentication.

about:flags
page for experimental features.- Can be prone to tampering. Clever users will be able enable features if the cookie names leak out.
✨ Next, let’s try to prevent unauthorised users from enabling feature flags.
Cookie-based feature flags can be cryptographically-signed to avoid tampering. This is done in a number of ways depending on the framework being used. Rails’s cookies.signed
appends the payload with an HMAC signature, similar to JWT tokens.
A number of third-party providers offer feature flag management. This brings features like to allow rolling out features by location, by AB split testing, and more. These can be used to make WIP features only available to internal team users, too.