// DevOps
Feature Flags Done Right: Ship Safely Without Long-Lived Branches
Separate deploy from release with feature flags, roll out progressively, and avoid the flag debt that turns a superpower into a trap.
Md Nayeem HossainOct 13, 20253 min read// DevOps
Separate deploy from release with feature flags, roll out progressively, and avoid the flag debt that turns a superpower into a trap.
Md Nayeem HossainOct 13, 20253 min readThe first time a team adopts feature flags, it feels like a superpower. Merge unfinished work to main, deploy anytime, turn features on for internal users first, kill a broken feature in seconds without a redeploy.
The second year, it often feels like a trap: hundreds of flags nobody remembers, code paths that are impossible to test, and an outage caused by flipping a flag that was supposed to be dead.
Here is how to get the first experience without the second.
The core idea is simple. Deploying puts code on servers. Releasing exposes behavior to users. Flags let you do them independently:
1if (await flags.isEnabled("new-checkout", { userId, plan })) {2 return renderNewCheckout(cart);3}4return renderLegacyCheckout(cart);This lets the team merge small changes to main continuously instead of maintaining a feature branch for weeks and fighting a giant merge at the end.
Different flags have very different lifetimes, and mixing them up causes most flag debt:
Tag every flag with its type and an owner when you create it.
A good release flag goes through stages:
Percentage rollouts must be sticky: hash a stable identifier so the same user always gets the same variant.
1function inRollout(flag: string, userId: string, percent: number) {2 const hash = murmurhash(`${flag}:${userId}`) % 100;3 return hash < percent;4}Including the flag name in the hash keeps different rollouts independent, so the same 1% of users are not the guinea pigs for every feature.
Flag checks run on hot paths, so they must be fast and resilient:
Every flag doubles the number of possible code paths. You cannot test every combination, but you can test the ones that matter:
The biggest risk with flags is never removing them. Old flags make code harder to read, and a forgotten flag can be flipped by accident. I have seen a stale flag re-enable a payment path that had been deprecated for a year.
Habits that prevent this:
Flipping a flag is a production change. It deserves the same controls as a deploy: an audit log of who changed what and when, permissions for production flags, and ideally a change note. When an incident starts, "what changed in the last hour?" must include flag changes.
Feature flags are one of the best tools for shipping safely, as long as you treat them as temporary by default. Separate deploy from release, roll out progressively with sticky hashing, define safe defaults, and delete flags as soon as they have done their job.