Low Level Design: Feature Flag Targeting Service

Feature Flag Targeting Service: Low Level Design

A feature flag targeting service lets teams release features safely by controlling visibility per user, segment, or percentage of traffic. The design covers data modeling, rule evaluation, SDK behavior, caching, and streaming updates.

Data Model

FeatureFlag Table

FeatureFlag
-----------
id            BIGINT PRIMARY KEY
key           VARCHAR UNIQUE NOT NULL
description   TEXT
type          ENUM('boolean','string','number','json')
default_value TEXT
enabled       BOOL NOT NULL DEFAULT false
created_at    TIMESTAMP

TargetingRule Table

TargetingRule
-------------
id              BIGINT PRIMARY KEY
flag_id         BIGINT REFERENCES FeatureFlag(id)
priority        INT NOT NULL
conditions      JSONB
variation_value TEXT
rollout_pct     INT CHECK (rollout_pct BETWEEN 0 AND 100)

Rules are ordered ascending by priority. The first rule whose conditions match the evaluation context wins.

Override Table

Override
--------
id              BIGINT PRIMARY KEY
flag_id         BIGINT REFERENCES FeatureFlag(id)
user_id         VARCHAR NOT NULL
variation_value TEXT
UNIQUE (flag_id, user_id)

Rule Evaluation Algorithm

Evaluation Order

evaluate(flag_key, context):
  1. Check Override table for (flag_id, context.user_id) → return if found
  2. If flag.enabled == false → return flag.default_value
  3. Load TargetingRules WHERE flag_id = flag.id ORDER BY priority ASC
  4. For each rule:
       if match_conditions(rule.conditions, context):
           if rule.rollout_pct == 100: return rule.variation_value
           bucket = hash(flag_key + context.user_id) mod 100
           if bucket < rule.rollout_pct: return rule.variation_value
  5. Return flag.default_value

Condition Attributes

Supported context keys for conditions: user_id, email, country, plan, and arbitrary custom attributes passed in the evaluation context object. Conditions are stored as JSONB and support operators: eq, neq, in, not_in, contains, semver_gte.

Sticky Bucketing

hash(flag_key + user_id) mod 100 is deterministic — the same user always lands in the same bucket for a given flag, producing consistent variation assignment across sessions without storing state.

SDK Design

Initialization

// Client fetches all flag configs for the user context on session start
GET /sdk/flags?context={user_id, country, plan, ...}
→ returns map of flag_key → variation_value (pre-evaluated server-side)

Local Evaluation

After bootstrap, the SDK caches flag configs in memory and evaluates locally on each isEnabled(flag_key) call — no network round-trip per evaluation. The SDK re-fetches on SSE update events.

Caching

Layer          Store    TTL     Key
Flag config    Redis    30s     flags:{flag_key}
All flags      Redis    30s     flags:all
Per-user eval  Local    session flags:{flag_key}:u:{user_id}

On flag update, the service publishes an invalidation event that clears the Redis key immediately rather than waiting for TTL expiry.

Streaming Updates

GET /sdk/stream   (SSE endpoint)
→ event: flag_updated
   data: {flag_key, new_variation_values}

Connected SDKs receive flag changes within under 1 second. The server pushes events over Server-Sent Events (SSE); the SDK re-evaluates affected flags and updates its local cache.

Audit Log

AuditLog
--------
id          BIGINT PRIMARY KEY
flag_id     BIGINT
actor_id    VARCHAR
action      VARCHAR   -- 'created','updated','deleted','toggled'
old_value   JSONB
new_value   JSONB
occurred_at TIMESTAMP

Every flag mutation (create, update, enable/disable, rule change) writes an immutable audit record. This supports compliance and rollback investigations.

Exposure Analytics

ExposureEvent
-------------
flag_key      VARCHAR
variation     TEXT
user_id       VARCHAR
exposed_at    TIMESTAMP

SDK fires an exposure event on first evaluation of each flag per session. Events are batched and flushed to a stream (e.g., Kafka) for downstream analysis — exposure counts, variation distribution, and funnel correlation.

Kill Switch

Setting enabled = false on a flag immediately returns default_value for all users, bypassing all rules. This is the fastest path to disable a feature in production without a code deployment.

Scale Considerations

  • Flag config is read-heavy — Redis handles evaluation cache; write path is rare.
  • SSE connections are long-lived; use a dedicated fan-out service (e.g., pub/sub) to push to thousands of connected SDK instances.
  • Override table lookups use a composite index on (flag_id, user_id) for O(1) lookups.
  • Exposure event ingestion uses async batching to avoid blocking flag evaluation.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What is a feature flag targeting service?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A feature flag targeting service controls which users see a feature by evaluating targeting rules against user attributes such as country, plan, or user ID. It enables safe deployments, percentage rollouts, and instant kill switches without code redeployment.”
}
},
{
“@type”: “Question”,
“name”: “How does percentage rollout work in a feature flag system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Percentage rollout uses a deterministic hash of the flag key and user ID modulo 100 to assign each user to a bucket. If the user's bucket number is less than the rollout percentage, they receive the treatment variation. This ensures the same user always gets the same variation — a property called sticky bucketing.”
}
},
{
“@type”: “Question”,
“name”: “What is sticky bucketing in feature flags?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Sticky bucketing ensures a user always sees the same variation for a given flag across sessions. It is achieved by hashing the flag key and user ID deterministically, so no session state needs to be stored. The same inputs always produce the same bucket assignment.”
}
},
{
“@type”: “Question”,
“name”: “How are feature flag changes pushed to SDKs in real time?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Connected SDKs maintain a Server-Sent Events (SSE) connection to a streaming endpoint. When a flag is updated, the server publishes a flag_updated event with the new configuration. SDKs receive this within under one second and refresh their local cache without requiring a full re-fetch.”
}
}
]
}

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering

See also: Anthropic Interview Guide 2026: Process, Questions, and AI Safety

Scroll to Top