Home › High-Level Design › Feature Flags
F

Feature Flags

If you can ship code without turning it on, you have split deploy from release. That one idea answers half of the deployment questions an interviewer will throw at you. I have pushed broken, half-finished code to production on purpose more times than I can count, not because I am sloppy, but because the code sat dark behind a flag where no user could reach it. Once that clicks, a lot of rollout and risk questions get much easier to reason about.

High-Level DesignBeginner10 min read

If you remember five things

The whole concept in one glance. Everything below defends these five calls.

  1. Deploy is not release. Deploy puts the code on the servers. Release lets users reach it. Flags let you do those on two different days.
  2. Evaluate in process. The SDK caches the full ruleset in RAM and streams updates, so every check is a microsecond lookup, not a network hop on the hot path.
  3. Sticky percentage rollouts. Bucket users with a deterministic hash of flag key plus user id, so ramping from 1 to 100 percent only grows the cohort and never reshuffles it.
  4. Fail to a safe default. If the control plane is unreachable, never throw. Fail closed for release flags, fail to off for kill switches, decided per flag.
  5. Flags have a lifecycle. Release flags are born to die. Operational flags live forever. Treating them the same is how you get 2^N untested code paths.

1The core idea#

A feature flag is a runtime switch. Your code checks a condition before it runs a path:

if (flags.isEnabled("new_checkout", user)) {
  renderNewCheckout();
} else {
  renderOldCheckout();
}

The load-bearing word is runtime.

  • The deploy already happened. Both code paths are in the binary.
  • What changes behavior is a value fetched from a flag service, not a new build.
  • So deploy means the code is on the servers, and release means users actually get the new behavior.
  • You can do those on two different days. I have deployed a feature on a Monday and not released it until Thursday, after the on-call rotation changed to someone who knew the code.
The split that reframes everything

Deploy and release are two separate events. Almost every rollout, canary, and risk question dissolves once you stop treating "the code shipped" and "users got it" as the same moment. Say this out loud early and the interviewer knows you have actually run this.

Flags come in four flavors, and interviewers like when you name them.

  • Release flags: hide unfinished work. Short lived, deleted after launch.
  • Experiment flags: A/B tests. Bucket users, measure, pick a winner.
  • Operational flags: kill switches and load shedding. Turn off the recommendation engine when the DB is on fire.
  • Permission flags: entitlements. The paid tier gets the export button.

Most flag bugs come from treating all four as the same thing.

  • A kill switch you forget to remove is fine. It is supposed to live forever.
  • An experiment flag left on for two years is tech debt that eventually breaks.
  • The difference is lifecycle: a release flag is supposed to die, an operational flag is supposed to live.
  • Store and review them the same way and you lose track of which is which.

2How it works#

The flag value has to reach your running code on every request, and it has to be fast, because you are adding a check to the hot path. This is where the real engineering lives.

flowchart LR Admin[Flag dashboard] --> CP[Control plane] CP --> CDN[(Edge config store)] CDN --> SDK[In process SDK] SDK --> App[App server] App --> Eval[Local evaluation]
The flag value flows one way: dashboard to control plane to the in-process SDK, which then evaluates locally. Notice there is no arrow from the app back to the control plane on the request path. That absence is the whole design.

The key decision: evaluate in process, not over the network per request.

  • The SDK pulls the full flag ruleset once, caches it in memory, and streams updates.
  • Every isEnabled call is then a local function on a map you already hold.
  • No per-request network dependency means the flag service can have a bad minute without your app having one too.
Pause before you read on: your service does 10,000 rps and each request checks 8 flags. If every check is a call to the flag service, what have you actually built?
Reveal the answer
80,000 network calls per second, and you have made your own p99 hostage to someone else's p99. You added a hard dependency to the hot path 80,000 times a second, and the one tool meant to reduce risk is now a new way for your site to go down. This is exactly why in-process evaluation wins.

Worked example: 10,000 rps across 20 instances, 8 flag checks per request.

  • Option A, call the flag service per check. 10,000 x 8 = 80,000 network calls per second. At 2ms per call best case, you add 2ms of latency to a request that may have taken 15ms, and you do it 8 times. Your p99 is now hostage to the flag service's p99. One more hop that can fail, 80,000 times a second.
  • Option B, in-process evaluation. The SDK holds the ruleset in RAM. An isEnabled call is a hashmap lookup plus a hash for percentage bucketing, roughly 1 to 5 microseconds. At 5 microseconds each, 80,000 evaluations is 0.4ms of CPU spread across 20 machines, so about 20 microseconds per machine per second. It does not register. Updates stream in over about 200ms.
~1 to 5 us
Per-flag-check cost in processA local hashmap lookup plus one hash, versus the ~2ms network call Option A pays per check. That six-order-of-magnitude gap is the entire argument for caching the ruleset in RAM.

Option B wins and it is not close.

  • The point of flags is to reduce risk, so adding a network call that can take your site down defeats the purpose.
  • The cost is a few seconds of staleness after a flag change, which is almost always fine.
  • Flip a kill switch and waiting 200ms for it to propagate is not the problem. The DB that is on fire is the problem.

Percentage rollouts have to be sticky.

  • You do not want a user flipping between old and new checkout on every page.
  • The standard trick is a deterministic hash:
bucket = hash(flagKey + ":" + userId) % 100
return bucket < rolloutPercent
  • Same user plus same flag always lands in the same bucket.
  • Ramp rolloutPercent from 1 to 5 to 25 to 100 and the cohort only grows, it never reshuffles. The user who hashed to bucket 3 is in at 5 percent, stays in at 25, stays in at 100.
  • Nobody who saw the new path ever gets yanked back to the old one.
Red flag that fails you here

Reaching for Math.random() to pick who gets the feature. It reshuffles the cohort on every page load, so a user flickers between old and new and your metrics turn to noise. I once spent an afternoon chasing a conversion dip that was nothing but a random rollout reshuffling the cohort. If you describe a rollout without the word "deterministic," expect the interviewer to poke it.

3Trade-offs#

The big architectural choice is where evaluation happens.

Approach Latency per check Failure blast radius Staleness Good for
Call flag service per request ~1 to 5ms network High, a flag service outage hits every request None, always fresh Rarely worth it
In-process SDK with streaming ~1 to 5 microseconds Low, cached value survives outages ~200ms to a few seconds The default choice
Edge or CDN evaluated ~0, part of edge logic Low Seconds to minutes Static or geo rules, no user context
Compile-time or env var 0 None Needs a redeploy Flags that never flip at runtime

Fail open or fail closed?

  • If the SDK cannot reach the control plane on boot and has no cached value, what does isEnabled return?
  • For a release flag, fail closed: keep the old safe path.
  • For a kill switch, fail to the safe state, which usually means the feature is off.
  • Pick per flag, with a sane default. This is the single most common follow-up, so have the answer ready before they ask.

Who can flip a flag?

  • A flag that changes money flow or data deletion needs an approval step and an audit log.
  • A UI color flag does not.
  • Treat flags like config changes, because they are, and config changes cause outages. I gate anything touching billing behind a two-person approval for exactly this reason.

Flag debt.

  • Every flag is a branch. N flags means up to 2^N code paths, and nobody tests all of them. 20 flags is 1,048,576 combinations.
  • Put an expiry on release flags and delete them.
  • I have seen a flag default flip years later and resurrect dead code. That is a real outage pattern, not a theoretical one.
From the interviewer's chair

When I ask about flags I am checking whether you think about operational safety, not just feature code. The fastest way to lose me is to describe the if-statement and stop. The fastest way to win me is to volunteer the failure mode ("if the control plane is down, here is what each flag returns") before I have to ask for it.

4Where it is used#

This is a core practice at basically every large shop, so name names in the interview.

  • LaunchDarkly is the canonical commercial product. Streaming SDKs, in-process evaluation, percentage rollouts. The model in section 2 is essentially theirs.
  • Statsig, Split, and Flagsmith are common alternatives, with Statsig leaning hard into experimentation and stats.
  • Facebook Gatekeeper and internal Google systems pioneered this at scale, gating features to employees, then 1 percent, then the world.
  • Unleash is the popular open-source option if you do not want a vendor.
  • Netflix uses flags heavily for region-by-region and device-by-device rollouts of streaming features.

You reach for flags when:

  • You want to merge to main continuously without releasing (trunk-based development needs flags to hide incomplete work).
  • You want progressive rollout to catch problems at 1 percent instead of 100.
  • You need a kill switch for a risky dependency.
  • You are running experiments.

If a prompt mentions "gradual rollout," "A/B test," "canary," or "kill switch," flags are a legitimate part of your answer.

5Common pitfalls#

  • Flags as permanent config. A kill switch living forever is fine. A release flag living forever is debt. Separate the two and expire the release ones.
  • Flaky percentage rollouts. A random number instead of a stable hash. Users flip-flop, metrics turn to noise, and you cannot tell if the new path is actually worse.
  • No fallback on SDK failure. If your code throws when the flag service is unreachable, you turned a safety tool into a new outage source. Always return a default, never throw.
  • Evaluating per request over the network. Covered above. It adds a hard dependency to the hot path and multiplies your QPS against someone else's service.
  • Flag explosion in tests. 20 flags is over a million combinations. You will never test them all. Keep the live count small and test the two paths that matter: all-on and all-off.
  • Leaking flag state to the client. If the client evaluates flags, a user can read which unlaunched features exist from the payload. For secret launches, evaluate server side and send only the resolved answer. A reporter reading your unreleased product names out of a JSON blob is a bad day.

6How it shows up in interviews#

Interviewers use flags to check whether you understand operational safety, not just feature code. The probes I hear most: how do you roll out to 1 percent and keep it sticky, what happens when the flag service is down, how do you kill a bad feature in seconds without a deploy, and how do you keep flags from becoming permanent mess.

Here is the contrast that separates levels.

Weak answer.

  • "I would add an if statement that checks a config value, and I can change the config to turn the feature on or off."
  • Not wrong, but it is the dictionary definition. It says nothing about latency, failure modes, stickiness, or lifecycle.
  • The interviewer will immediately poke the holes: what config, read how often, what happens when it is unreachable?

Strong answer.

  • In-process SDK evaluation, so each check is a microsecond-level local lookup, not a network call, because I do not want the flag service to be a hard dependency on the hot path. At 10,000 rps and 8 checks each that is 80,000 network calls a second, which is 80,000 new ways to fail.
  • Rollout percentage comes from a stable hash of flag key plus user id, so a user stays in the same bucket as I ramp 1 to 5 to 25 to 100.
  • The SDK caches the last known ruleset and fails to a safe default if the control plane is unreachable: fail closed for release flags, fail to off for kill switches.
  • Gate the risky dependency behind an operational flag so we can shed it in 200ms without a deploy, and put an expiry on release flags so they get deleted after launch to avoid 2^N untested code paths.

Why the second answer wins.

  • It hits deploy-versus-release, the latency decision with a reason and a number, stickiness, failure behavior, and lifecycle.
  • It is mostly the same facts the weak answer had, arranged around trade-offs instead of a definition.

The junior names the switch. The senior names what happens when the switch is unreachable.

One more thing that scores points: say explicitly that flags are how trunk-based development and continuous delivery actually work. You merge unfinished code to main daily, hidden behind a flag, instead of living on a long branch that rots. Connecting flags to the delivery model shows you see the system, not just the switch.

Follow-ups the interviewer will ask (and your answers)
"The flag service is completely down. What does your app do?"
Nothing breaks. The SDK serves its last cached ruleset from RAM, so evaluation keeps working with slightly stale values. If there is no cache at all (cold boot during an outage), each flag returns its configured default: fail closed for release flags, fail to off for kill switches. The rule is never throw.
"How do you roll out to 1 percent of users and keep it stable?"
Bucket with a deterministic hash of flag key plus user id, modulo 100, and include the user if the bucket is below the rollout percent. Ramping from 1 to 5 to 25 to 100 only grows the cohort because the hash is stable per user, so nobody who saw the new path gets pulled back. Random bucketing would reshuffle every request and ruin the metrics.
"Why not just call the flag service on every request so values are always fresh?"
Because it makes the flag service a hard dependency on your hot path. At 10,000 rps with 8 checks each that is 80,000 network calls a second, adding milliseconds and a failure mode to every request. In-process evaluation makes each check a microsecond lookup and survives a flag-service outage. The cost is a few seconds of staleness, which is fine for almost every flag.
"How do you kill a bad feature in seconds without a deploy?"
That is exactly what an operational flag is for. Flip it in the dashboard, the control plane streams the change, and SDKs pick it up in about 200ms across the fleet. No build, no redeploy, no waiting on CI. The code path was always there; you just closed the gate.
"You have 20 flags in one service. What is the risk?"
Up to 2^20, over a million code-path combinations, and you test maybe two of them. The fix is lifecycle discipline: release flags get an expiry and are deleted after launch, operational flags are the small set you keep, and tests cover all-on and all-off rather than the full combinatorial space. Leaving dead release flags around means a default can flip years later and resurrect dead code.

What you can now explain in the room

  • Why deploy and release are two separate events, and what flags buy you by splitting them.
  • Why in-process evaluation beats per-request calls, with the 80,000-calls-a-second number behind it.
  • How a deterministic hash makes percentage rollouts sticky, and why random bucketing ruins metrics.
  • What fail open versus fail closed means per flag type when the control plane is down.
  • Why the four flag types have different lifecycles, and how that prevents 2^N flag debt.
  • How flags let you kill a bad feature in ~200ms without a deploy, and enable trunk-based development.

If this clicked, try the related Design Interview Question on designing a progressive rollout and canary release system.

Get a prep plan built around your Feature Flags interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Feature Flags prep plan is on the way.