Back to Insights
Feature FlagsJuly 25, 20267 min read

Feature Flags: Separate Deployment From Release

Feature flags decouple shipping code from exposing behavior. Learn flag semantics, safe targeting, observability, testing, rollback, and disciplined cleanup.

John Mather, Founder of Prime Axiom AI
John Mather

Founder, Prime Axiom AI

Published July 25, 2026

Technical illustration for Feature Flags: Separate Deployment From Release
Feature Flags: Separate Deployment From Release — Prime Axiom Insights.

Feature Flags: Separate Deployment From Release

Deployment puts code in an environment. Release makes behavior available to users. Treating those as one event forces every rollout decision into a binary deploy-or-rollback choice. A feature flag inserts a controlled decision point so teams can deploy dormant code, expose it gradually, compare behavior, and disable it without rebuilding.

That power has a cost. Flags create runtime branches, distributed configuration, and temporary states that tests and operators must understand. Safe flag systems are not collections of booleans; they are a governed release control plane.

A checkout migration scenario

Suppose a retailer is replacing tax calculation during checkout. The change touches pricing, invoices, refunds, and customer display. A flag can first enable the new calculator for internal test accounts, then a deterministic percentage of low-risk traffic, then selected regions. The old calculator remains available while reconciliation compares outputs.

mermaid
flowchart LR
  D[Deploy both paths] --> I[Internal cohort]
  I --> C{Checks pass?}
  C -- no --> O[Disable new path]
  C -- yes --> P[1 percent cohort]
  P --> R{Business and technical signals}
  R -- outside bounds --> O
  R -- healthy --> W[Widen exposure]
  W --> F[Make new path default]
  F --> X[Remove flag and old path]

The diagram includes the frequently omitted final step: cleanup. A rollout is incomplete while both implementations remain permanent.

Model a flag as a decision

A useful evaluation receives a key, typed default, context, and provider state, then returns a typed value plus reason and variant. OpenFeature standardizes an API and evaluation concepts so application code is less coupled to one vendor.

import { OpenFeature } from '@openfeature/server-sdk'

const client = OpenFeature.getClient('checkout') const useNewTax = await client.getBooleanValue( 'checkout-new-tax', false, { targetingKey: customer.id, region: customer.region, accountType: customer.accountType, } )

const quote = useNewTax ? await newTaxCalculator.quote(cart) : await legacyTaxCalculator.quote(cart)

This TypeScript example supplies a safe false default. The targeting key should be stable so one customer does not oscillate between variants. Context should contain only attributes approved for release targeting; avoid sending names, email addresses, or secrets merely because a provider accepts arbitrary values.

Wrap evaluation near the behavior decision, not throughout low-level functions. A single branch at the tax adapter boundary is easier to reason about than dozens of checks. Do not ask the flag provider repeatedly during one transaction; evaluate once and carry the chosen variant so a configuration update cannot split a checkout between implementations.

Flag types and lifecycles

TypePurposeTypical completion
ReleaseGradually expose a new pathnew path wins; flag removed
ExperimentCompare defined variantsanalysis ends; winning behavior implemented
OperationalShed load or disable an optional capabilityretained with drills and ownership
PermissionEnforce an entitlementmanaged as long-lived policy, not casual release debt

Do not confuse release targeting with authorization. A flag can hide a user interface, but the server must still enforce permissions. Experiment assignment also needs analysis integrity; changing cohorts midstream can invalidate interpretation.

Every flag needs an owner, description, creation date, expected removal condition, safe default, allowed contexts, and risk class. Time is not the only cleanup trigger. “Remove after all regions use the new calculator and one full reconciliation period passes” is more actionable than an arbitrary date.

Deterministic percentage rollout

Percentage assignment should hash a stable subject and flag key into buckets:

import hashlib

def enabled(flag_key: str, subject: str, percentage: float) -> bool: raw = f"{flag_key}:{subject}".encode("utf-8") bucket = int.from_bytes(hashlib.sha256(raw).digest()[:8], "big") % 10_000 return bucket < round(percentage * 100)

This Python function uses 10,000 buckets, so 1.25 percent maps to 125 buckets. Including the flag key prevents unrelated flags from creating identical cohorts. This demonstrates deterministic assignment, not a complete evaluation engine: production systems also need configuration versions, context rules, telemetry, and provider failure handling.

If users collaborate in an account, account-level assignment may be safer than individual assignment. Choose the unit that prevents inconsistent shared state. For a database migration, route writes by entity rather than request so one record never alternates formats.

Failures and defaults

Evaluation can fail because the provider is down, configuration is malformed, context is missing, or a type mismatches. The default must be valid and safe. During a rollout, the old path is often safest; after migration, leaving the old path as default can resurrect retired behavior during an outage. Update default semantics as lifecycle stages change, then remove the flag.

Cache configuration locally when the SDK supports it and understand staleness. A kill switch requiring immediate propagation has different needs from an experiment assignment. Record evaluation reason and configuration version without logging sensitive context.

Some operations must not change variant midway. Capture an evaluation snapshot:

{ "flagKey": "checkout-new-tax", "variant": "new", "configVersion": "cfg-184", "subject": "account-opaque-id", "evaluatedAt": "request-start" }

Persist this snapshot with a long-running order if later steps need the same decision. It provides audit evidence and makes retries consistent.

Testing walkthrough: both paths and the control plane

First unit-test behavior with an in-memory flag evaluator. Test old and new paths, not the vendor SDK:

test.each([ [false, legacyTaxCalculator], [true, newTaxCalculator], ])('uses selected calculator', async (flag, expected) => { flags.boolean.mockResolvedValue(flag) await checkout.quote(cart) expect(expected.quote).toHaveBeenCalledTimes(1) })

This parameterized test proves branch selection. Add assertions that the nonselected calculator was not called and that one request evaluates once.

Next contract-test flag keys, types, defaults, and required context against the real provider in a nonproduction project. Integration-test provider timeout, stale cache, malformed values, and configuration changes during a transaction. End-to-end tests should cover representative targeting rules but should not attempt every percentage bucket.

Before widening exposure, test the rollback path. For the tax scenario, disable the flag while traffic is active, confirm new requests use the old adapter, and decide what happens to checkouts already using the new variant. Rollback may be unsafe after a one-way schema change; feature flags do not reverse data migrations automatically.

Release plan as executable policy

rollout: flag: checkout-new-tax stages:

  • audience: internal
  • percentage: 1
  • percentage: 10
  • percentage: 50
  • percentage: 100

advance_when: min_observation_window: defined-by-owner error_rate_delta: within-approved-bound reconciliation_delta: within-approved-bound stop_when:

  • invoice_mismatch
  • latency_budget_breach

owner: checkout-team

This YAML-like configuration expresses sequence and guard categories without inventing numeric outcomes. Real thresholds must come from baselines and business tolerance. Advancement should require enough observation to cover the workload’s cycles, not simply a few quiet minutes.

Observability by variant

Attach flag key, variant, evaluation reason, and configuration version to traces and metrics with controlled cardinality. Compare request errors, latency, conversion funnel steps, reconciliation mismatches, and support contacts by variant. A global average can look stable while the new cohort fails.

Do not use raw user IDs as metric labels. Keep assignment details in sampled, access-controlled traces or an audit store. Alert on provider failures, unknown flags, type mismatches, missing targeting keys, unexpected assignment distribution, and evaluation latency.

A release timeline might read:

10:00 deploy version containing both calculators 10:08 internal variant evaluations begin 10:20 reconciliation signal outside approved bound 10:21 flag disabled; no deployment rollback 10:24 trace links mismatch to rounding rule

This is a fictional diagnostic example, not a reported outcome. It illustrates why configuration changes and application traces need a shared correlation timeline.

Debt and failure patterns

Nested flags create combinatorial states. Avoid making one temporary flag depend on another. Limit concurrent release flags in a capability and visualize mutually exclusive variants. Static analysis can find keys no longer referenced; dashboards can identify flags fixed at one variant.

Common failures are using flags as authorization, choosing an unstable targeting key, evaluating repeatedly during one operation, shipping one-way migrations behind a reversible-looking switch, omitting a safe default, and keeping the old path forever. Another is changing experiment allocation while interpreting results without accounting for exposure.

Cleanup walkthrough

After 100 percent exposure and the agreed observation period, search code, configuration, dashboards, runbooks, and tests for the key. Remove the old branch and obsolete migrations, simplify tests to the surviving behavior, deploy, and only then archive provider configuration. Reversing that order can make old application versions fail during a rolling deployment. Confirm no supported client or background job still evaluates the key.

Record the final decision and measurements used to complete the rollout. Cleanup is a behavior change and deserves review; deleting the wrong default can reintroduce the retired path.

Archive the decision record with the surviving implementation.

Multi-model routing is a practical place to apply controlled assignments. Testing modern systems helps balance branch tests with broader confidence, and Prime Axiom resources covers implementation support.

Conclusion: release is a lifecycle

Feature flags make deployment calmer only when the release process becomes more disciplined. Use stable targeting, typed evaluation, explicit defaults, variant-aware telemetry, rehearsed rollback, and named cleanup conditions. Treat every flag as temporary complexity unless it is deliberately operated as a permanent control. The goal is not more flags; it is smaller, observable, reversible release decisions.

Authoritative references

Apply the idea

Turn technical clarity into a working system.

Talk with Prime Axiom about architecture, AI automation, integrations, and the operational workflow behind your next build.

Continue reading

Technical illustration for Python Asyncio: Concurrency for I/O-Bound Work
Python Asyncio: Concurrency for I/O-Bound Work — Prime Axiom Insights.
Python

July 25, 2026 · 7 min read

Python Asyncio: Concurrency for I/O-Bound Work

Asyncio improves I/O-bound throughput when tasks spend time waiting, but production safety depends on bounded concurrency, cancellation, deadlines, and blocking-code isolation.

By John Mather
Read article →
Technical illustration for TypeScript for Reliable Boundaries, Not Decorative Types
TypeScript for Reliable Boundaries, Not Decorative Types — Prime Axiom Insights.
TypeScript

July 25, 2026 · 7 min read

TypeScript for Reliable Boundaries, Not Decorative Types

TypeScript is most valuable when it makes trusted states precise and forces untrusted input through runtime validation, rather than decorating unsafe values with assertions.

By John Mather
Read article →
Technical illustration for Web Accessibility Engineering: Build Inclusion Into Components
Web Accessibility Engineering: Build Inclusion Into Components — Prime Axiom Insights.
Accessibility

July 25, 2026 · 7 min read

Web Accessibility Engineering: Build Inclusion Into Components

Accessibility is a component engineering discipline spanning semantics, keyboard behavior, focus, visual presentation, assistive technology, automated tests, and manual walkthroughs.

By John Mather
Read article →