top of page

Designing a Feature Flag System

  • Writer: Pradeep P
    Pradeep P
  • 3 days ago
  • 4 min read

Layer 6 · Post 5 of 26

Layer 6 — Modern systems · Post 67 of 88

A feature flag system lets you turn code paths on or off for specific users without redeploying, which is how modern teams ship and experiment safely.

What you'll learn

  • Why flags are a distributed config problem (evaluation close to the request, not a DB hit per check)

  • How targeting, percentage rollouts, and a kill switch actually get evaluated

  • What goes wrong with consistency, stale SDKs, and flags that never die

The idea in one minute

A feature flag is a named switch plus rules: "on for employees," "on for 5% of users in India," "off everywhere." Your code asks if flags.on("new_checkout", user) and takes a path. Changing the flag does not require a deploy.

Admin UI / Git / API | v [ Flag store + audit log ] --publish--> [ CDN / streaming config ] | +-------------------------------------+ | SDK poll / stream | v v [ App servers / clients ] [ Edge / workers ] | | evaluate locally evaluate locally | store unreachable? last-known-good snapshot, fail closed for risky flags

The interview design is not SELECT enabled FROM flags on every request. That couples your p99 to the flag service and will take you down when you need the kill switch most.

Why it matters

Modern shipping is: merge behind a flag, deploy dark, open to 1%, watch errors, then 100% or rollback the flag — not the binary. Experimentation (A/B) is the same machinery with a variant instead of a boolean.

If the flag service is in the request path and it dies, you either freeze the world on old values or accidentally enable a half-finished checkout. Interviewers want that failure story.

How it works

  1. Control plane. Product and eng set flags: key, type (bool, string, JSON), rules (user IDs, attributes, percentage), and environments (dev/stage/prod). Writes are audited. This store is small.

  2. Distribution. A publisher pushes a snapshot (or a stream of diffs) to every evaluator: server SDKs, mobile, edge. Snapshots are versioned. SDKs poll every few seconds or subscribe (streaming).

  3. Evaluation. The SDK hashes flag_key + user_key into a bucket (0–99) for percentage rollouts so the same user is stable. Rules are ordered: kill switch / targeting overlays beat the default. Evaluation is CPU-local and microseconds.

  4. Contexts. You pass a context: user_id, country, plan, app_version. Never send secrets you would not log. Client-side flags are visible to the user — do not hide a pricing algorithm in a mobile flag.

  5. Failure. SDK uses last known good. For a dangerous feature, fail closed (treat missing config as off). For a performance optimization, fail open might be fine. Name the default in the interview.

  6. Experiments. A flag with variants plus an assignment that you log. The experiment platform is analytics on top of the same assignment function.

Clients (admin) write flags. APIs of your product only read via SDK. Stores are the flag DB plus a cache/CDN of snapshots. Failure is partition from the control plane — local snapshot must still evaluate.

A simple example

You ship a new search ranker behind search_v2. Rules: on for employee=true; then 5% of users whose hash falls in 0–4. A user in the 5% hits any of your 40 app servers and always gets v2 because the hash is deterministic. Error rate spikes. You flip the flag off in the UI. Within the poll interval (or immediately on stream), SDKs evaluate false. You did not roll back the deploy.

An intern checks flags.on("search_v2") with an extra Postgres call "to be sure." During a flag-service blip, checkout waits on that query. That is the anti-pattern.

Common mistakes

Evaluating in a remote API on every if. Flags must be local. The control plane can be down.

Non-sticky percentages (random() each request). Users flicker between old and new UI. Hash the user.

Flags that live forever. Every flag is a bit of extra cyclomatic complexity. Expiry dates and "remove after 100%" are process, but mention them.

Sensitive logic only in client flags. Attackers read the binary. Server-side enforcement still required.

No audit log. "Who turned on payments_v3 in prod?" is an incident question.

How this shows up in real systems

  • LaunchDarkly, Split, Unleash, Flagsmith, ConfigCat: control plane + SDKs.

  • Facebook/Gatekeeper, Uber/experiments: in-house, same snapshot idea.

  • Kubernetes / app config: related, but flags add per-user targeting, not only per-cluster YAML.

Layer 2's "config reload" meets Layer 4's consistent hashing (sticky buckets). Next: work that must run at a time, not on a user request — schedulers.

Recap

  • Ship snapshots to SDKs; evaluate in-process with a stable hash.

  • Fail closed or open on purpose. Last-known-good beats a hard dependency.

  • Treat flags as product surface: targeting, audit, and a plan to delete them.

Jobs that must fire at 02:00 across a fleet need a different control plane.

Layer 6 · Post 5 of 26

Comments


About Me

DSC_7604.jpg

Hi, I am a software engineer from Bangalore, India. Love spending time on gaming and photography. This website is where I will ocassionally throw what comes to my mind. Hope it is useful or at least entertaining to you. :)

 

  • Instagram
  • Facebook
  • Twitter
  • LinkedIn
  • YouTube
  • 500px

© 2023 by Going Places. Proudly created with Wix.com

bottom of page