Feature flag questions, answered

Short answers to the things that go wrong with feature flags in practice. Each one names the causes in the order they actually occur, and points at the doc that proves it.

Can I use one SDK key for every environment?

No, and the key works nothing like a credential you could reuse anyway. It selects an environment: whichever one issued the key is the configuration your process receives. Point staging at the production key and staging reads production flag states, so a rollout you start in staging is a rollout in production.

Do I need a user ID to evaluate a feature flag?

Not for on/off flags or targeting rules that match on other attributes. You need one for percentage rollouts, because the bucket comes from hashing the identifier with the flag key. Without it a server SDK evaluating locally serves the first (control) variation, while the hosted evaluation endpoint spreads anonymous traffic by weight with no per-user stickiness.

Do users lose a feature if I lower the rollout?

Yes. Buckets are fixed per user, so lowering a rollout from 50% to 20% keeps everyone in buckets 0 through 19 and removes everyone from 20 through 49. It is deterministic, not a reshuffle, which is why raising the percentage again restores exactly the same people. Users who lose access see the feature disappear mid-session unless you evaluate once per session and cache the result.

Does changing a targeting rule re-bucket users?

No. A user bucket comes from a salt stored on the flag and the user key, so editing rules, adjusting weights or adding conditions leaves every assignment untouched. Two things do reassign everybody: changing the attribute a rollout buckets by, and deleting a flag and recreating it, because a new flag gets a new salt.

How do I test code that uses feature flags?

Use the test client each SDK provides. You construct it with a map of flag keys to values and it evaluates against that map, with no network calls and no initialization step. It is not registered in the normal client cache, so every call returns an independent instance and tests cannot leak state into each other.

Is automated flag removal safe to let into my repo?

The Action runs inside your own CI, so the only credential holding write access to your repository is the GITHUB_TOKEN that workflow already has. The Featureflip token you supply reads removal candidates and nothing else, and a Viewer token scoped to one project is enough. Every change arrives as a pull request you review, and dry-run mode prints each diff without contacting GitHub at all.

What if I archive a flag still used in my code?

Archiving removes the flag from the configuration SDKs receive, so every remaining call site immediately falls back to the default value passed at that call site. Nothing throws and nothing logs an error, because the SDK cannot tell an archived flag from one that never existed. Remove the code first, then archive.

Why can't my browser SDK see a feature flag?

The flag is not marked client-side visible. Client SDK keys are public by design, so they only ever receive flags you have explicitly opted in. A server key sees every flag in the project, which is why the same flag resolves from your backend and comes back empty in the browser. The client SDK reports it as not found rather than raising.

Why did my flag change take minutes to reach my app?

Streaming delivers a flag change within seconds, so anything slower is not coming over the stream. When the stream cannot connect, most SDKs give up after five failed attempts and poll instead, every thirty seconds by default. A stream held open by a buffering proxy goes quiet without raising an error, so the SDK keeps waiting on a connection that delivers nothing.

Why did my targeting rule not match?

An earlier rule almost certainly matched first and stopped evaluation. Failing that, the attribute your rule tests never made it into the evaluation context, or the value is there but arrives as a different type or case than the rule expects. Rules run top to bottom and the first match wins, which makes rule order part of the logic.

Why didn't the cleanup Action remove my flag?

Almost always because your code reads the flag through your own wrapper function instead of calling the SDK directly. Matching is by method name plus key string, so a wrapper is invisible to the rules and the run reports no changes. The other common causes are a refusal it prints explicitly, such as a mock stub holding the flag read, or a key that is not a string literal at the read site.

Why do teammates get different variations?

A rollout buckets by user key by default, and each user hashes independently. Two colleagues in the same workspace count as two separate users, so a 50% rollout splits them roughly half the time. Point the rollout at an account or organization attribute instead, and everyone sharing that value moves together.

Why do two SDKs return different variations?

One of them is almost certainly evaluating a keyless context. Bucketing needs a stable user key, and the two evaluation paths handle its absence differently on purpose. A server SDK evaluating locally serves the first variation, while the hosted endpoint assigns a fresh random bucket per call. Pass the same user key everywhere and the two agree, since every SDK runs the same hash.

Why do two users get different variations?

In a percentage rollout that is the expected behavior. Each user is hashed together with the flag key to produce a bucket from 0 to 100, so a 30% rollout puts roughly three users in ten on the new variation. The same user always lands in the same bucket for the same flag. Hashing is per flag, so being inside the 30% for one tells you nothing about any other.

Why does my flag flash the wrong value on load?

Your UI is rendering inside the window before the browser SDK has its first flag payload, and during that window every call returns the default value you passed. The reason string reads FlagNotFound rather than an error, since the flag is simply absent from an empty snapshot. Wait for initialization before rendering the gated region, or render a neutral state until the client is ready.

Why does my flag work in dev but not production?

Every environment holds its own configuration, so enabling a flag in development changes nothing in production. Targeting rules and rollout percentages are per environment as well, which means a flag can read as on in both and still serve different values. A new environment starts every existing flag in the off state deliberately, so nothing goes live by accident.

Why does my flag work locally but not in Docker?

The SDK key almost certainly did not reach the container. A key read from a local env file that is never passed through in your compose file or Dockerfile arrives empty, and the SDK serves your default value rather than failing loudly. After that, suspect outbound network. A container on an internal-only network cannot reach the evaluation endpoint, so the first payload never arrives.

Why doesn't my rollout hit the percentage I set?

Bucketing assigns each user a fixed number from 0 to 99 and counts how many land under your threshold, rather than metering traffic. With a few hundred users the observed share can sit several points either side of the configured one, and that is expected. Persistent skew usually means anonymous contexts, which the hosted endpoint spreads randomly on every call instead of bucketing.

Why doesn't my segment match any users?

The rule probably cannot resolve the segment at all, which fails closed and matches nobody. A client SDK never given segment data behaves the same way. Once that is ruled out, look at condition logic. Segments carry one flat condition list with a single AND or OR, so a segment built to catch two different user groups with AND matches nobody by construction.

Why doesn't my semver targeting rule match?

The value you are sending does not parse as a semantic version. An unparseable string matches nothing at all, with no fallback to text comparison, which is why the rule looks dead instead of behaving oddly. A leading v is fine and 2.0 compares equal to 2.0.0, but a build number like 1042 or a date will never satisfy a semver condition.

Why is my feature flag returning the default value?

The SDK almost certainly cannot find the flag at all, so evaluation never got far enough to fail. Check the SDK key first, since it may belong to a different environment, then whether the flag is simply off there. After that comes a misspelled key, a client still initializing, a type mismatch against the variation, or a flag that is not client-side visible when you ask from a browser.

Why is my feature flag returning the wrong type?

The SDKs do not coerce between types. If the variation holds a string and you call the boolean method, you get the default you passed and no conversion happens. The dashboard then shows the flag serving one thing while your code reports another, which reads like a stale cache and is really a type mismatch.

Why is my flag evaluation returning an error?

The evaluator picked a variation key the flag no longer defines, which normally means a rule or fallthrough still references a deleted variation. The SDK serves the default value you passed and reports Error, declining to guess which survivor you meant. A variation that exists but holds a null value falls outside this, so the reason string reliably separates a config problem from a data one.

Looking for something longer?

The documentation covers every SDK in depth, the glossary defines the vocabulary, and the blog works through full patterns end to end.

Try it yourself

The free Solo plan covers 10 flags and 2 environments, which is enough to reproduce everything on this page.