Lesson 13 of 30
Deploying code and releasing a feature don't have to be the same event — a flag lets you ship dark, then turn it on for whoever you choose.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Every lesson so far has treated "deploy" and "release" as one action: merge, pipeline runs, code goes live, users see the new behavior. A feature flag breaks that assumption on purpose. It's a runtime toggle — usually just an if statement backed by a config value you can flip without a new deploy — that decides whether a given chunk of code actually runs for a given user.
That split matters because it separates two genuinely different decisions. Deploying is an engineering decision: is this code correct, tested, safe to have running in production. Releasing is a product decision: is this feature ready for users to see, and which users. Once a flag exists, code can be deployed dark — sitting in production, fully live, doing nothing because the flag is off — and released later, to 1% of users, then 10%, then everyone, entirely independent of any deploy. This is why teams that use flags well stop fearing "deploying on Friday": the risky part isn't the deploy anymore, it's the flip of the flag, and that's a decision you can make (and reverse) in seconds, whenever you want, without touching the pipeline.
// checkout/CheckoutPage.tsx
import { useFeatureFlag } from "@/lib/flags";
export function CheckoutPage() {
const newCheckoutEnabled = useFeatureFlag("new-checkout-flow");
return newCheckoutEnabled ? <NewCheckoutFlow /> : <LegacyCheckoutFlow />;
}
// flag config, served from a flags service or a simple config table — not a redeploy
{
"new-checkout-flow": {
"enabled": true,
"rollout": {
"percentage": 10,
"stickyBy": "userId"
},
"allowlist": ["internal-qa-team", "beta-tester-cohort"]
}
}
The commit that adds NewCheckoutFlow merges, passes CI, and deploys through the normal pipeline like anything else — at that point it's live in production code but effectively invisible, because rollout.percentage is 0. Turning it on for the internal QA team, then 10% of real users, then 100%, is a config change against the flags service, not a new pipeline run. If the 10% cohort shows elevated checkout failures, someone flips enabled back to false — instantly, with no rollback, no redeploy, no waiting on CI.
if (flagOn) means there are now two real code paths in production simultaneously — old and new — and both need to actually work, not just the one someone's actively developing.stickyBy: "userId" in the example above matters — without sticky bucketing, the same user could see the new flow on one request and the old one on the next, which is confusing and makes bug reports useless.The deployment-strategies lesson's canary rollout gradually shifts infrastructure traffic between an old and new version of a whole deployed service. A feature flag does the same gradual-exposure trick, but one level up — inside a single running version of the app, deciding which users see which code path. Same underlying idea (limit blast radius, watch, then widen), different layer of the stack.