Skip to content

Feature flags and their lifecycle

Deploy ships code switched off; a flag edit releases it or takes it back; each kind has a lifetime, and a leftover flag is debt.

Predict first

Before you read: predict

The shop deploys build 7.3 of its checkout service to every server at 09:00. The build contains a new express checkout:

At 09:00 the flag system holds:

{ "express_checkout": { "state": "ENABLED", "variants": { "on": true, "off": false }, "defaultVariant": "off",
targeting": { "if": [ { "in": [ { "var": "country" }, ["GB"] ] }, "on", "off" ] } } }

At 09:30 the product owner edits the flag in its dashboard so that the list reads ["GB", "IE"]. Nothing is deployed or restarted. At 09:45 three shoppers check out, from GB, IE and FR.

Who gets express checkout?

Code for this question
const client = OpenFeature.getClient();export async function checkoutPage(shopper) {  const express = await client.getBooleanValue('express_checkout', false, { country: shopper.country });  return express ? 'express checkout' : 'standard checkout';}
Pick one

The 30-second version

A feature flag lets one deployed build run old or new code, chosen at runtime from its configuration and the request's context. Deploying ships the code switched off; releasing is a configuration edit, and so is taking it back. Release, experiment, ops and permission flags live from days to years. A flag past its job is debt.

Deploy ships code switched off; a flag edit releases it or takes it back; each kind has a lifetime, and a leftover flag is debt.

How it works

Step 1 of 13

A flag wraps a code path. At this toggle point the code asks the flag system which path to run for this request.

Analogy: A swimming pool's new water flume

The flume is installed and plumbed during the closed weeks, but the gate at its top stays chained until the duty manager unlocks it.

At the pool In the code
the flume is built and tested, but the top gate stays chained deployed, not released (step 3)
the manager unchains the gate on opening day, and chains it again if a joint leaks; nobody rebuilds the flume release and take-back by configuration (step 4)
in trial week, only members whose card number ends in 0 may ride; then cards ending in 0, 1 or 2 a bucket from a stable key, widened (step 5)
a club-only lane that has run for years a permission flag (step 6)
the lifeguard's pump stop, pressed on any overcrowded afternoon a long-lived kill switch (step 7)
the "opening soon" chain still hanging a year later, and nobody sure whether the flume is meant to be open flag debt (step 11)

In real terms: a flag is a decision the running code makes on each evaluation, from configuration that changes without a new build.

Breaks down when: a chained gate is visible to everyone. A flag's state is not: two users can be on different paths at once, and the code still holds both paths.

Contrasting case: a flag read from an environment variable at boot is still a flag, but each change needs a restart or a redeploy. That can suit a release flag, but not a kill switch, which must act at once.

Examples, run for real

4.1 ExamHall: the AI alerts that only a redeploy could stop

EXH-2100: build 409 shipped the proctor console's new AI alerts with no flag, so they went live for every institution at deploy time. A false-positive storm followed, and the way back was to redeploy build 408. MR !2400 wraps them in new_proctor_ai_alerts, evaluated per request with the proctor's institution.

4.1 build 409 deployed · flag off · north-college sees AI alerts: no · south-academy: no
4.1 edit: target north-college · north-college: yes · south-academy: no · build 409
4.1 edit: targeting removed · north-college: no · south-academy: no · build 409 · deploys in all: 1

The build never changes. Enabling the alerts for one institution is one edit (steps 2–4), and so is switching them off for everyone.

4.2 Proof domain, checkout: three flags, eight checkouts

The checkout page reads three independent boolean flags. The script sets every combination in the flag system and renders the page each time.

4.2 flags 3 · states 2^3 = 8 · distinct checkout pages 8 · states an all-off + all-on suite covers: 2 of 8

Each flag doubles the states (step 10). A suite that tests only all-off and all-on covers two of the eight pages that production can serve.

4.3 ExamHall: a 10% rollout by proctor id

new_proctor_ai_alerts uses flagd's fractional rule on the proctor id. The script evaluates it ten times for each of 500 proctors.

4.3 500 proctors x 10 evaluations · proctors who got both answers: 0 · proctors with AI alerts: 53 (10.6%)

The bucket comes from a hash of the proctor id, so nobody flips between versions (step 5). It gives about 10%, not exactly 50 proctors.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 5

one_click_pay is rolled out with flagd's fractional rule, which buckets each shopper by targetingKey, the shopper id. On Monday the flag system holds:

{ "one_click_pay": { "state": "ENABLED", "variants": { "on": true, "off": false }, "defaultVariant": "off",
targeting": { "fractional": [ ["on", 5], ["off", 95] ] } } }

On Tuesday the team edits the rule to [["on", 20], ["off", 80]]; nothing else changes. The 2,000 shoppers u0001 to u2000 each check out once on Monday and once on Tuesday.

How many of Monday's one-click shoppers lose it on Tuesday, and how many shoppers have it on Tuesday?

Pick one

Question 2 of 5

ExamHall's upload service has this module:

The service starts at 08:00, when the flag system holds:

{ "disable_video_upload": { "state": "ENABLED", "variants": { "on": true, "off": false }, "defaultVariant": "off" } }

At 08:55 video storage is overloaded, and the engineer on duty sets disable_video_upload to on in the flag dashboard. 25 sessions opened before 08:55 and 15 after; each sends one video after 08:55. All 40 videos are stored.

Which one change makes that edit stop all 40, with no restart or deploy, while a video sent before 08:55 is still stored?

Code for this question
const client = OpenFeature.getClient();const uploadsOff = await client.getBooleanValue('disable_video_upload', false); export async function handleUpload(session, video) {  if (uploadsOff) return { status: 503 };  return store(session, video);}
Pick one

Question 3 of 5

The checkout computes VAT with:

and the flag system holds:

{ "new_tax_engine": { "state": "ENABLED", "variants": { "on": true, "off": false }, "defaultVariant": "on" } }

new_tax_engine has served on to every shopper for 8 months, and the team retires it. The goal: no basket's VAT changes at any step of the cleanup, and afterwards no code reads new_tax_engine.

Which plan meets both?

Code for this question
const client = OpenFeature.getClient();export async function vat(basket, ctx) {  const useNew = await client.getBooleanValue('new_tax_engine', false, ctx);  return useNew ? newTax(basket) : oldTax(basket);}
Pick one

Question 4 of 5

On a normal day the box office's seat map shows every fan a live heat map of demand. The flag heatmap_off hides it. This is the flag's whole change log:

2025-11-14 09:02  on   all fans  set by on-call: seat-db CPU alarm
2025-11-14 09:41  off  all fans  set by on-call
2026-03-02 10:00  on   all fans  set by on-call: seat-db CPU alarm
2026-03-02 10:35  off  all fans  set by on-call
2026-09-19 09:00  on   all fans  set by on-call: seat-db CPU alarm
2026-09-19 09:52  off  all fans  set by on-call

No rule on the flag has ever named a fan, a group or a user attribute.

Which kind of flag is heatmap_off?

Pick one

Question 5 of 5

The admin console's flag inventory lists four flags:

  • grading_ui_v2 wraps the rebuilt grading screen, and has served on to every institution for 9 months;
  • pro_plan_analytics shows the analytics tab only to institutions on the paid plan, and was added 3 years ago;
  • pause_bulk_export lets the engineer on duty stop bulk result exports while the database is overloaded; added 2 years ago, it is used on each results day;
  • results_layout_b sends half the proctors to a new results layout; it has run for 3 weeks, and its result is not yet significant.

Which flag is debt today?

Pick one

Go deeper

Pro tips
  1. Give every flag an owner and an expiry date in its metadata, and make CI fail while code still reads a flag past its expiry. When: creating any flag (step 11). Verify: flag-expiry: FAIL invoice_pdf_v2 (owner billing-team, expired 2026-02-01) still read in src/billing/invoice.ts, exit 1; the flag that has not expired passes. Status: RAN.
Source

openfeature.dev did not answer from this machine that day (connect timeout), so the OpenFeature specification and the flagd docs were read from their source files in the CNCF open-feature GitHub repositories. Vendor and SDK behaviour can change: what the scripts show is SDK 1.23.0 and flagd-core 4.0.1. Valid as of 2026-10-01.

Checked against the tools and docs as of 2026-10-01.