Design a Feature Flag Service
Flags, targeting rules, rollout percentages, and evaluation — a rules-engine + strategy design.
Problem Statement
Design a Feature Flag Service similar to LaunchDarkly. Product teams define flags with named variations, ordered targeting rules, user segments, and optional percentage rollouts. Application code asks the service to evaluate a flag for a given UserContext and receives the resolved variation.
The core LLD challenge is the evaluation engine: it must apply rules in a predictable order while staying closed for modification. Adding a rule such as country targeting, segment targeting, plan targeting, or time-window targeting should mean adding a new rule class, not editing a long evaluator switch statement.
Business context
Feature flags decouple deployment from release. Teams can ship dormant code, open it to beta users, roll it out to 5% of traffic, kill it quickly, or run an experiment without a redeploy.
Interviewers use this problem to test whether you can design a small rules engine: deterministic rollout, ordered precedence, stable user bucketing, extensible rule strategies, and change notifications for SDKs or services that cache flag definitions.
Functional Requirements
Create or update a flag with a key, enabled state, allowed variations, and a default variation.
Evaluate a flag for a user context and return exactly one variation or a caller-supplied fallback when the flag is missing.
Support ordered targeting rules; the first matching rule wins.
Support user segments such as beta-testers, employees, or enterprise-customers.
Support percentage rollouts that deterministically bucket the same user into the same variation for a given flag.
Allow new targeting rule types to be added without changing the evaluation engine.
Notify listeners when a flag is inserted or updated so SDK caches can refresh.
Non-Functional Requirements
Deterministic evaluation
The same flag key and user key must resolve to the same rollout bucket until the rollout weights change. Random per-request assignment breaks user experience and experiments.
Open for extension
Rule behavior belongs behind TargetingRule. The engine walks an ordered chain and does not know whether a rule checks a segment, attribute, plan, or device.
Low latency
Evaluation should be in-memory and proportional to the number of rules on one flag, not to all flags in the system.
Thread safety
Application threads may evaluate while an admin thread updates a flag. The service uses concurrent collections and immutable flag snapshots.
Operational visibility
Production systems need metrics for evaluated flags, missing flags, rule matches, rollout distribution, and stale SDK caches.
Requirement Clarification
QAre variations only booleans?
No. Model variations as strings in the base design so boolean flags, experiment variants, and configuration choices all fit. A production version can wrap them in a typed value object.
QWhere does segment membership come from?
For LLD, segment membership is already present on UserContext. A production service may hydrate it from an identity service or segment store before evaluation.
QWhat happens when a flag is disabled?
The evaluator immediately returns the flag's default variation. Disabled means no targeting rules or rollouts are applied.
QHow should percentage rollout be computed?
Hash flagKey:userKey into a stable bucket from 1 to 100, then compare that bucket with cumulative variation weights.
QDo we need REST APIs, authentication, or persistence?
Not in the core model. The design focuses on object responsibilities. Persistence, admin APIs, RBAC, and audit logs are production layers around the same domain model.
UML Class Diagram
Sequence Diagram
Entity Identification
FeatureFlag
Immutable flag definition. Holds the key, enabled state, allowed variations, default variation, ordered targeting rules, and fallback rollout.
TargetingRule
Strategy interface for a single targeting decision. A rule either returns a variation or returns empty so the next rule can try.
PercentageRollout
Deterministic bucketing policy. It maps flagKey:userKey to bucket 1..100 and selects the variation whose cumulative weight covers that bucket.
UserContext
Immutable snapshot of the caller being evaluated: stable user key, custom attributes, and segment membership.
FlagEvaluator
Stable evaluation engine. It owns precedence and chain traversal but delegates all rule-specific logic to TargetingRule implementations.
FeatureFlagService
Singleton facade over flag storage, evaluation, and update notifications. Application code talks to this boundary rather than the raw map.
FlagChangeListener
Observer contract used by SDK caches, audit sinks, or streaming layers that react to flag changes.
Design Patterns Used
Each TargetingRule is a strategy for one kind of match: segment, attribute, plan, region, time window, or anything added later. The evaluator depends only on the interface.
A flag owns an ordered chain of targeting rules. The evaluator asks each rule in order and stops at the first returned variation, preserving explicit business priority.
FeatureFlagService notifies FlagChangeListener subscribers after a flag is upserted. SDK caches and audit streams react without being hard-coded into the service.
The in-memory demo uses one FeatureFlagService instance so all callers read the same flag registry. In production, the same boundary could wrap a distributed store.
Step-by-Step Design
1Separate flag definition from flag evaluation
A FeatureFlag is just data: allowed variations, default, rules, and rollout. It does not decide precedence. This keeps mutation, validation, and evaluation responsibilities clean.
public final class FeatureFlag { private final List<TargetingRule> rules; private final PercentageRollout rollout; public List<TargetingRule> getRules() { return rules; } public PercentageRollout getRollout() { return rollout; } }2Make every rule a strategy
The evaluator should never know rule internals. A new rule type implements TargetingRule and returns an Optional variation. Empty means the chain should continue.
public interface TargetingRule { Optional<String> evaluate(UserContext context, FeatureFlag flag); String describe(); }3Evaluate rules as an ordered chain
Business teams expect rule order to matter. Put precise allowlists first, broader attribute rules later, and percentage rollout last.
for (TargetingRule rule : flag.getRules()) { Optional<String> variation = rule.evaluate(context, flag); if (variation.isPresent()) { return variation.get(); } }4Use deterministic percentage rollout
Hash flagKey:userKey into a bucket from 1 to 100. As rollout moves from 10% to 25%, users already in the first 10 buckets remain enabled and only new buckets are added.
private int bucket(String flagKey, String userKey) { CRC32 crc = new CRC32(); crc.update((flagKey + ":" + userKey).getBytes(StandardCharsets.UTF_8)); return (int) (crc.getValue() % 100) + 1; }5Expose a singleton service boundary
The service hides the registry map, validates the missing-flag fallback path, and emits update events. Application code should call evaluate, not manipulate flags directly.
6Notify observers after state changes
Listeners are called after the new flag snapshot is stored. That ordering prevents subscribers from reading stale state when they refresh their local caches.
Complete Java Implementation
Explanation of Every Class
FeatureFlag
Immutable flag snapshot containing all data needed for evaluation: key, enabled state, allowed variations, default, ordered rules, and rollout. It validates the default variation up front.
TargetingRule
Strategy interface plus example rule classes. AttributeEqualsRule checks context attributes, while SegmentRule checks segment membership. Both return empty when they do not match.
PercentageRollout
Deterministic rollout strategy. It uses CRC32 over flagKey:userKey, maps the result to bucket 1..100, and applies cumulative weights.
UserContext
Immutable evaluation input. The fluent methods return new contexts, preventing one request from mutating the attributes or segments used by another request.
FlagEvaluator
Stable engine that enforces precedence: disabled default, ordered targeting chain, percentage rollout, then default. It never switches on rule type.
FeatureFlagService
Singleton facade with an in-memory concurrent registry, evaluation entry point, and observer notification hook for flag changes.
Main
Small demo that registers a flag, adds a listener, defines segment and attribute rules, and evaluates three different user contexts.
Dry Run
Sample input
Flag new-checkout has variations on/off, default off, rules SegmentRule(beta-testers -> on) then AttributeEqualsRule(country=IN -> on), and rollout 25% on / 75% off.
| Step | Context | Rule chain result | Rollout result | Returned variation |
|---|---|---|---|---|
| 1 | maya in beta-testers, country=US | SegmentRule matches | Skipped | on |
| 2 | ravi not in segment, country=IN | AttributeEqualsRule matches | Skipped | on |
| 3 | alex not in segment, country=BR | No rule matches | Stable bucket lands in first 25 | on |
| 4 | missing flag for chen | No flag loaded | Not evaluated | caller fallback off |
The run shows the precedence order. Explicit segment targeting beats attribute targeting, attribute targeting beats rollout, and missing flags are handled by the service fallback instead of throwing.
Complexity Analysis
| Operation | Time | Space | Note |
|---|---|---|---|
| evaluate flag | O(R + W) | O(1) | R targeting rules are checked until a match; W rollout weight entries are scanned only when no rule matches. |
| upsert flag | O(1 + L) | O(1) | Concurrent map update plus notifying L listeners. |
| build user context | O(A + S) | O(A + S) | Immutable copies for A attributes and S segment keys keep evaluation inputs safe. |
| percentage bucket | O(K) | O(1) | K is the combined length of flag key and user key fed into the hash. |
Evaluation is intentionally local to one flag. If a flag accumulates hundreds of rules, index segment rules or compile rules into a decision tree, but keep the public evaluator contract unchanged.
Extensibility
New rule type
Add a class such as PlanRule, EmailDomainRule, or TimeWindowRule that implements TargetingRule. FlagEvaluator stays untouched.
Typed variations
Replace string variations with a VariationValue object that can hold boolean, number, string, or JSON-like payloads. The rule contract can still return the selected variation key.
External segments
Resolve segment membership before building UserContext, or introduce a SegmentProvider used by SegmentRule. Keep remote I/O outside the hot evaluator path.
Advanced rollout algorithms
Swap PercentageRollout for experiments, prerequisite flags, or mutually exclusive experiments. The evaluator still treats rollout as the final strategy after rules.
Persistence and audit
Store immutable flag versions in a repository and have FeatureFlagService upsert snapshots from that repository. Listener events can include old and new versions.
Alternative Designs
Expression tree rules
Represent targeting as an expression tree with AND, OR, NOT, and leaf predicates. The evaluator recursively evaluates the tree and returns a variation at matching leaves.
Tradeoffs
More powerful for product teams, but harder to explain and validate in a machine-coding interview than an ordered rule chain.
Central decision table
Flatten rules into table rows with columns for segment, country, plan, and variation. Evaluation scans rows and chooses the first row whose predicates all match.
Tradeoffs
Easy for simple admin UIs, but adding new predicate types often changes the table schema and evaluator logic.
Remote SDK cache
Move storage and evaluation data to a remote control plane. Application SDKs keep an in-memory cache and evaluate locally, refreshing via streaming updates.
Tradeoffs
Better production latency and availability, but introduces distributed cache consistency, streaming retries, and stale-data handling.
Common Mistakes
- ×
Putting a switch on rule type inside FlagEvaluator, which violates the open-closed goal of the problem.
- ×
Using random numbers for percentage rollout, causing a user to flip between variations across requests.
- ×
Ignoring rule order and combining all matches, which makes explicit targeting unpredictable.
- ×
Returning null for missing flags instead of a clear caller fallback or default variation.
- ×
Letting UserContext be mutable while multiple threads evaluate flags against it.
- ×
Not validating that rules and rollouts return only allowed variations.
- ×
Calling observers before storing the new flag snapshot, causing listeners to refresh stale data.
Follow-up Interview Questions
QHow would you support multivariate experiments?
Keep variations as named keys and let PercentageRollout hold multiple weighted variations such as A 50%, B 25%, C 25%. Metrics and experiment analysis are separate systems fed by evaluation events.
QHow do you keep rollout stable when increasing from 10% to 25%?
Use deterministic buckets. Users in buckets 1..10 stay enabled, and buckets 11..25 are newly enabled. Never reshuffle all users on each percentage change.
QWhere do prerequisite flags fit?
Add a PrerequisiteFlagRule that asks an evaluator for another flag's result, with cycle detection and a max depth. The main engine still sees it as a targeting rule.
QHow would you scale this across services?
Serve flag snapshots from a control plane, let SDKs evaluate locally, and push updates through streaming or polling. The same evaluator can run inside every SDK process.
QHow do you audit flag changes?
Version every flag update, record actor, timestamp, old value, new value, and change reason. Emit observer events after persistence so audit trails and cache invalidations are consistent.
Production Considerations
Persistence and versioning
Store flag definitions as immutable versions. Evaluators should receive a complete snapshot so a request never observes half of an update.
SDK caching
Production SDKs should evaluate locally from a cache and refresh by streaming updates or polling. Remote calls during every request would add latency and outage coupling.
Observability
Emit evaluation counts by flag, variation, rule id, and fallback reason. Alert on sudden missing-flag spikes or unexpected variation distribution.
Governance
Track owners, descriptions, expiry dates, and cleanup status. Stale flags become hidden complexity and can keep dead code alive for years.
Security
Admin mutations need RBAC, approvals for production environments, and audit logs. Client-side SDKs must receive only flags safe to expose to end users.
What Interviewers Look For
Did you clearly separate flag data, rule strategies, evaluation engine, and service boundary?
Can you add a new rule type without editing FlagEvaluator?
Is percentage rollout deterministic per flag and user?
Did you explain rule ordering and first-match-wins semantics?
Did you include cache invalidation or observer notifications for flag updates?
Did you name the missing-flag and disabled-flag fallback behavior?
Quiz
0/5 answered
1.Why should **FlagEvaluator** not switch on concrete rule type?
2.What does first-match-wins mean for targeting rules?
3.Why hash **flagKey:userKey** for rollout instead of only **userKey**?
4.Which pattern is represented by **FlagChangeListener**?
5.What should happen when a flag is disabled?
Practice Variants
Add prerequisite flags
AdvancedImplement a PrerequisiteFlagRule that matches only when another flag resolves to a required variation. Add cycle detection to prevent recursive evaluation loops.
Add rule groups with AND and OR
IntermediateSupport compound targeting such as country=IN AND plan=PRO, while preserving the same TargetingRule contract for the evaluator.
Add flag version audit events
AdvancedExtend the service to persist old and new flag versions, then send observers a structured change event with actor, timestamp, and reason.
Flashcards
Cheat Sheet
Entities: FeatureFlag, TargetingRule, PercentageRollout, UserContext, FlagEvaluator, FeatureFlagService, FlagChangeListener.
Patterns: Strategy for rules, Chain of Responsibility for ordered first-match evaluation, Observer for change notifications, Singleton for the demo service boundary.
Evaluation order: missing flag -> caller fallback; disabled flag -> default; targeting rules -> first matching variation; rollout -> deterministic bucket; otherwise default.
Determinism: hash flagKey:userKey into bucket 1..100; cumulative rollout weights choose the variation.
Extensibility: add a new rule by implementing TargetingRule. Do not edit FlagEvaluator for country, plan, device, segment, or time-window rules.
Production: SDK local cache, immutable flag versions, audit log, RBAC for admin changes, metrics by flag/rule/variation, cleanup workflow for stale flags.
References
- DocsLaunchDarkly Documentation — Feature flags and targeting — LaunchDarkly
- BlogFeature Toggles — Martin Fowler
- BookHead First Design Patterns (Strategy and Observer) — Freeman & Robson
- DocsRefactoring Guru — Chain of Responsibility