Advanced feature flags — targeting, gradual rollout, and edge evaluation?
609 views
78 of 106
1
The problem
Question presented to candidate:
"Say our flag system already delivers rules to every SDK. How does a flag actually decide what one user gets? Walk me through targeting rules, how a 10% rollout stays stable as we ramp to 100%, and what changes if we evaluate flags at the CDN edge instead of in our servers."
What a strong answer should cover:
A fixed evaluation order: off switch, prerequisites, individual targets, rules top to bottom (first match wins), then the fallthrough rollout, with the matched reason returned for debugging
Deterministic bucketing: hash of flag key, a per-flag salt and the bucketing key into a fixed range (LaunchDarkly documents 100,000 buckets), so the same user always gets the same answer without storing assignments
Why ramps are monotonic (raising 10% to 25% only adds users) and why a different salt per flag keeps rollouts of different flags independent; bucketing by organisation or device instead of user when consistency matters at that level
Gradual rollout as a controlled process: stages, guardrail metrics, automatic rollback, and stable assignment for experiments
Edge evaluation: lower latency and privacy (browsers get evaluated values, not rules), but eventually consistent config per location, so users can flip between versions unless you pin a version; and flag-dependent responses must not be cached as one shared page
Clarifying questions expected:
Who is the unit of rollout: a user, an organisation, a device, a request?
Is this a release (just ramp safely) or an experiment (needs stable assignment and statistics)?
Which edge platform, and how fast does its config storage propagate?
Code / implementation expected: Optional — the examples implement rule evaluation and hash bucketing in four languages, and simulate edge propagation with version pinning.
feature-flags
edge
experimentation
2
Try it yourself
Sketch your approach before you read the solution. That is what the interview tests.
Stuck? Get a hint.
A short nudge toward the approach. It will not give the answer away.
3
Solution
Target Audience: Intermediate engineers who know what a feature flag is (see the feature flag system design) and want the evaluation internals; every term is still defined.
Difficulty: Hard
Verified with: Node 22.22, Python 3.12, Go 1.24 and Java 21 for the bucketing example (identical output); Node and Python for the edge example. LaunchDarkly bucketing facts are from the LaunchDarkly percentage-rollouts documentation and Cloudflare KV propagation from the Cloudflare storage overview, both checked October 2026.
How to read this doc: Plain language first, then 📌 Interview term: callouts with the exact vocabulary. Every count below comes from the example programs; the Verified section quotes their full output. The hashing scheme in the code is our own; it follows the doc
The solution is hidden so you can try first
10 min read · 10 sections
4
Read the code
Targeting rules and sticky percentage rollouts: rule order, SHA-1 buckets, monotonic ramps, per-flag independence, bucketing by org
// Edge flag evaluation: eventually consistent config at each PoP, a user hopping between PoPs,// version pinning to stop flip-flops, and what a browser should (not) be sent.// Config versions. v2 is the kill switch: new-checkout off for everyone.const configs = { 1: { "new-checkout": true }, 2: { "new-checkout": false } };
// Assumed propagation delay of v2 to each PoP after publish at t=0 (KV-style store: up to about 60 s).const pops = [
{ name: "SYD", delay: 5 },
{ name: "SIN", delay: 22 },
{ name: "LAX", delay: 58 },
];
constversionAt = (pop, t) => (t >= pop.delay ? 2 : 1);
// The user moves between PoPs (mobile network, anycast route change): one request every 4 s.functionsimulate(pinning) {
let seen = 0, flips = 0, last = null, originCalls = 0;
const trace = [];
for (let t = -8, i = 0; t <= 64; t += 4, i++) {
const pop = pops[i % pops.length];
let v = versionAt(pop, t), where = pop.name;
if (pinning && v < seen) { v = seen; where = "origin"; originCalls++; } // edge is older than the client: ask originconst value = configs[v]["new-checkout"];
if (last !== null && value !== last) flips++;
last = value; seen = Math.max(seen, v);
trace.push(`t=${t}s ${where}:v${v}:${value ? "on" : "off"}`);
}
return { trace, flips, originCalls };
}
for (const pinning of [false, true]) {
const r = simulate(pinning);
console.log(pinning ? "== with version pinning (a cookie carries the highest config version seen) ==" : "== without version pinning ==");
for (let i = 0; i < r.trace.length; i += 4) console.log(" " + r.trace.slice(i, i + 4).join(" "));
console.log(` value changes seen by the user: ${r.flips} requests sent to origin: ${r.originCalls}`);
}
// What to ship to a browser: the rules, or only the evaluated values for this user?const flagsCount = 200;
const rulesConfig = {};
const evaluated = {};
for (let f = 0; f < flagsCount; f++) {
const targets = [];
for (let u = 0; u < 50; u++) targets.push("user-" + (f * 1000 + u));
rulesConfig["flag-" + f] = {
on: true, salt: "s" + f, targets,
rules: [{ attr: "email", op: "endsWith", values: ["@bigcustomer.com"], serve: 0 }],
rollout: [{ variation: 0, weight: 10000 }, { variation: 1, weight: 90000 }],
};
evaluated["flag-" + f] = f % 3 === 0;
}
const rulesBytes = JSON.stringify(rulesConfig).length;
const valuesBytes = JSON.stringify(evaluated).length;
console.log("== client payload for 200 flags ==");
console.log(` full rules: ${rulesBytes} bytes (exposes 10000 targeted user keys and customer domains)`);
console.log(` evaluated values: ${valuesBytes} bytes (${(rulesBytes / valuesBytes).toFixed(1)}x smaller, nothing to leak)`);
// Latency for a page that needs its flags before rendering (assumed round-trip times).const userToPop = 20, popToOrigin = 120, evalMs = 0.05;
console.log("== time to know the flags for one page ==");
console.log(` evaluate at origin: ${userToPop + popToOrigin + evalMs} ms evaluate at edge: ${userToPop + evalMs} ms`);
5
Discussion
Other approaches, follow-ups and how it went in real interviews.