# What Are Feature Flags? A Field Guide for Platform Teams

URL: https://upstreamapi.com/journal/what-are-feature-flags
Type: blog
Locale: en
Published: 2026-08-29
Updated: 2026-08-31

---

> Feature flags let you ship code to production without exposing it to users. Here is a precise, operational guide to how they work and where the failure modes actually live.

What are feature flags? They are conditional checks in your application code that control which code paths execute for a given user, session, or environment, without shipping a new build. A flag named `new_checkout_flow` set to `false` for 99% of your user base means you deployed that code two weeks ago. You just have not turned it on yet. The contract they establish is precise: deployment and release become two separate events. For any team doing continuous deployment at scale, that contract is load-bearing.

## Deployment and Release Are Not the Same Event

Most teams learn this distinction after a bad rollout. A feature ships Tuesday afternoon, something in the request trace starts behaving differently by Wednesday morning, and by the time someone opens an investigation the diff covers four commits and two service boundaries. Attributing causality in that scenario is genuinely hard.

Feature flags force precision. When the code behind a flag is merged and deployed, it sits inert in production. You confirm the deployment succeeded, observe baseline metrics for a few hours or days, and verify nothing degraded. Then you flip the flag for 1% of traffic. You now have a single attributable variable. If error rate climbs on the flagged path, you are not debugging a code diff. You are toggling a config value.

That separation is not primarily a speed argument. It is a blast radius argument. A release affecting 1% of sessions that goes wrong is recoverable in minutes. A release affecting 100% of sessions that goes wrong is a major incident.

The mechanism itself is straightforward. In TypeScript, a flag evaluation looks roughly like this:

`const showNewCheckoutFlow = flagClient.variation(
  'new_checkout_flow',
  { userKey: session.userId, custom: { plan: user.plan } },
  false // default if flag service unreachable
);

if (showNewCheckoutFlow) {
  return renderNewFlow(cart);
}
return renderLegacyFlow(cart);`The `false` default is not a formality. It is the behavior your users get if the flag evaluation service has a network partition. Define it deliberately, not by accident.

## The Four Flag Types That Actually Matter in Production

Not all feature flags serve the same operational purpose. Treating them identically in your codebase and your management tooling is a reliable path to confusion during incidents and accumulation of flag debt.

**Release flags** gate new features during development and rollout. They are expected to be temporary: created when work starts on a feature branch, removed once the feature reaches 100% of users and the team has confirmed stability. Release flags with no removal date and no assigned owner become permanent furniture in your codebase.

**Experiment flags** power A/B tests and multivariate experiments. They tie to analytics cohort identifiers and their lifecycle is bounded by the experiment. When the experiment concludes, the flag goes with it. The common mistake: keeping the winning variant behind the flag indefinitely, reasoning that removal is "not urgent." Two years later, the experiment flag is part of the critical path and nobody remembers which variant is active.

**Ops flags** are kill switches and circuit breakers. Unlike release flags, they are designed to be permanent infrastructure. A `disable_ml_recommendations` flag that lets you bypass a slow ML inference layer when its SLO degrades is something you want available at 3am without reading documentation. These flags should evaluate locally, have a well-documented fallback, and be tested regularly under normal operations, not discovered under pressure.

**Permission flags** control access by user tier, account plan, or beta cohort. They are long-lived by design. The confusion risk: permission flags often look like release flags to a reader who does not know the history. A clear naming convention matters more here than anywhere else.

![Progressive software rollout visualization showing percentage-based traffic routing with concentric node circles](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/upstreamapi/2026-08/e28abc-inline1.webp)

## A Percentage Rollout Without a SLO Gate Is Just Slow Deployment

Here is where most feature flag implementations stop short. The platform team wires up a rollout schedule: 1% on Monday, 5% Tuesday, 25% Wednesday, 100% Friday. They document it, share it with stakeholders, and call it progressive delivery.

But "progressive" without a validation condition at each step is deferred risk, not reduced risk. The percentage dial controls exposure. It does not validate safety.

What makes a staged rollout operationally meaningful is the SLO gate between each stage. Before advancing from 5% to 25%, something should answer: is the error rate on the flagged code path within the SLO budget? Is p99 latency holding within the same band as the control group? Is the error budget burning faster than baseline?

If none of those questions are instrumented, the rollout schedule is a timeline, not a validation loop.

A setup that holds in production: define two SLO evaluation windows. A short window (15 minutes) catches fast failures - a bad database query, a schema mismatch, a regression in a critical path. A longer window (24 hours or one full traffic cycle) catches gradual degradation - memory leaks, cache pressure, edge cases in low-frequency traffic segments. Require both windows to show green before any advancement. If either window is violated, halt the rollout and page the on-call.

The percentage is a dial. The SLO window is the gate. Both are required.

## Kill Switch Engineering: Design It Before You Need It

The kill switch is not a fallback. It is a first-class design decision that should exist before the first line of feature code is written.

A kill switch designed under pressure is a kill switch with unexamined assumptions. You are testing it for the first time during an active incident, inside a terminal session opened from a PagerDuty notification, with five people watching a Slack thread. That is the worst possible moment to discover that your ops flag targets users by session ID and your session service is currently degraded.

![SLO monitoring dashboard with error budget burn rate charts and kill switch indicator for feature rollout control](https://fdzlnqpwsaniezitwiuw.supabase.co/storage/v1/object/public/cms-media/upstreamapi/2026-08/dd1477-inline2.webp)

Three properties that are non-negotiable for any kill switch intended for production use:

- 
**Sub-50ms local evaluation**: the flag check cannot be a network call to a remote evaluation service. If the evaluation depends on a service that could itself be degraded, you have a circular dependency in your incident response path.

- 
**Explicit fallback value**: what does the flag return when the flag service is unreachable? This must be documented, defined in code, and tested. "Whatever the SDK default is" is not an answer.

- 
**Tested under dependency failure**: kill switches should be part of your chaos engineering rotation. Validate them against scenarios where auth is degraded, where the flag service itself is down, and where network latency to the evaluation endpoint exceeds 2 seconds.

The teams that execute incident response cleanly are the ones that rehearsed. The kill switch is part of the runbook. Make it boring.

## Flag Debt: The Technical Debt Nobody Puts on the Roadmap

Teams that adopt feature flags aggressively often accumulate what is sometimes called flag debt: flags that completed their purpose but were never removed. The feature shipped, the experiment concluded, the beta ended. The flag stayed.

At 50 flags, this is a minor nuisance. At 500 flags across a distributed system, it is an active operational risk. Each flag is a code branch that requires maintenance, testing, and comprehension during incident investigation. A developer trying to understand a failure at 2am does not want to trace through 40 conditional branches to find the relevant one.

One pattern observed across multiple platform teams: flag evaluation middleware appearing in the top five stack frames for p99 latency. The cause in most cases is stale flags with complex targeting rules that evaluate dozens of conditions per request, carrying the weight of decisions made two years prior that nobody felt comfortable deleting.

The countermeasure is organizational rather than technical. Every flag created should have three attributes: an owner, a type (release, experiment, ops, permission), and an expected removal date. Release flags should be removed within two sprints of full rollout. Experiment flags should be removed when the experiment concludes, not "when someone gets around to it." The flag inventory should be auditable on demand and surfaced in engineering health dashboards.

## Targeting Granularity: The Dimension That Breaks at Enterprise Scale

Most feature flag platforms support percentage-based rollouts and basic user attribute targeting. The gap that surfaces at enterprise scale is targeting granularity: the ability to express rollout rules that are specific enough to be useful without becoming complex enough to be unmaintainable.

A useful targeting hierarchy for production rollouts at a platform team level:

- 
**Environment-level**: production, staging, preview. The first gate, not the only one.

- 
**Infrastructure segment**: data center, availability zone, or Kubernetes cluster. Useful for isolating geographic blast radius.

- 
**Account or tenant**: for B2B SaaS platforms, rolling out per-account is often safer than rolling out per-user-percentage, because you can observe a full account's traffic pattern rather than a statistical sample.

- 
**User cohort**: beta users, internal users, power users by activity tier.

- 
**Session attribute**: useful for experiment flags, dangerous for ops flags.

The platforms that implement this well (LaunchDarkly and Statsig are the most cited by SRE teams doing this at scale) allow complex rule composition without requiring engineering time to modify the targeting logic during an active rollout. That self-service capability is the difference between a 30-second rollout pause and a ticket to the platform team.

The platforms that implement this poorly force you to choose between targeting granularity and operational simplicity. That tradeoff will surface at 3am.

## What the Post-Mortem Keeps Finding

Every post-mortem for a feature-related production incident asks the same set of questions. Was the feature gated behind a flag? Was the rollout progressive? Was there a validation condition between stages? Was the kill switch tested before the incident?

If all four answers are yes, the incident is a calibration problem: thresholds set too loosely, targeting rules with an edge case, SDK evaluation behavior under network partition that was not accounted for. These are solvable with configuration changes and runbook updates.

If any answer is no, the incident is an architecture problem. Feature flags are not a debugging convenience. They are a deployment architecture decision. That decision either exists before the feature ships, or it does not exist at all.

The question worth answering before the next release: what would the post-mortem say about this rollout if something goes wrong tonight?

## FAQ

### What is a feature flag in simple terms?

A feature flag is a conditional check in your application code that controls whether a specific feature is active for a given user, environment, or session. It lets you deploy code to production without exposing it to users, then control the rollout independently of the deployment.

### What is the difference between a feature flag and a feature toggle?

The terms are often used interchangeably. In most practical usage, a feature toggle is the implementation mechanism (a boolean switch in code), while a feature flag refers to the broader system including targeting rules, rollout percentages, and management tooling built around that toggle.

### How do feature flags reduce deployment risk?

Feature flags reduce blast radius by limiting exposure during rollout. Instead of releasing to 100% of users at once, you gate the release to 1% and validate metrics before advancing. If the flagged code path shows elevated error rates or latency degradation, you toggle the flag off without a code revert.

### What is feature flag debt and how do you prevent it?

Feature flag debt is the accumulation of stale flags that completed their purpose but were never removed. It increases code complexity, adds untested branches, and creates operational confusion during incidents. Prevention requires assigning an owner, type, and expected removal date to every flag at creation time.

### What is a SLO gate in the context of feature flag rollouts?

A SLO gate is a validation condition that must be satisfied before a rollout advances to the next percentage stage. Typically defined as: error rate and p99 latency on the flagged code path must stay within the SLO budget for a defined evaluation window (e.g., 15 minutes and 24 hours) before the rollout can proceed.

### How should feature flag kill switches be designed?

Kill switches require local evaluation (no network round-trip to a remote flag service), an explicitly defined fallback value for when the flag service is unreachable, and regular testing under dependency failure scenarios. A kill switch that has never been validated outside an incident will have untested failure modes.

### What are the main types of feature flags used in production?

The four main types are: release flags (gate features during development and rollout, meant to be temporary), experiment flags (power A/B tests, expire with the experiment), ops flags (permanent kill switches and circuit breakers for incident response), and permission flags (control access by user tier or account plan, long-lived by design).